From d20fce83e3451a97f7ebc0063268b98e8bd1ca3c Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Tue, 28 Oct 2025 07:21:47 -0700 Subject: [PATCH 001/111] warm up porting --- clus/src/TaggerCheckSTM.cxx | 277 ++++++++++++++++++++++-------------- 1 file changed, 173 insertions(+), 104 deletions(-) diff --git a/clus/src/TaggerCheckSTM.cxx b/clus/src/TaggerCheckSTM.cxx index 402a27d8..9ec741db 100644 --- a/clus/src/TaggerCheckSTM.cxx +++ b/clus/src/TaggerCheckSTM.cxx @@ -125,114 +125,183 @@ class TaggerCheckSTM : public IConfigurable, public Clus::IEnsembleVisitor, priv // Process each main cluster size_t stm_count = 0; - // // validation check ... temporary ... - // { - // auto boundary_indices = main_cluster->get_two_boundary_steiner_graph_idx("steiner_graph", "steiner_pc", true); - - // const auto& steiner_pc = main_cluster->get_pc("steiner_pc"); - // const auto& coords = main_cluster->get_default_scope().coords; - // const auto& x_coords = steiner_pc.get(coords.at(0))->elements(); - // const auto& y_coords = steiner_pc.get(coords.at(1))->elements(); - // const auto& z_coords = steiner_pc.get(coords.at(2))->elements(); - - // // Add the two boundary points as additional extreme point groups - // geo_point_t boundary_point_first(x_coords[boundary_indices.first], - // y_coords[boundary_indices.first], - // z_coords[boundary_indices.first]); - // geo_point_t boundary_point_second(x_coords[boundary_indices.second], - // y_coords[boundary_indices.second], - // z_coords[boundary_indices.second]); - // geo_point_t first_wcp = boundary_point_first; - // geo_point_t last_wcp = boundary_point_second; - - // std::cout << "End Points: " << first_wcp << " " << last_wcp << std::endl; - // last_wcp = geo_point_t(215.532, -95.1674, 211.193); - - // auto path_points = do_rough_path(*main_cluster, first_wcp, last_wcp); - - // // Create segment for tracking - // auto segment = create_segment_for_cluster(*main_cluster, path_points); - - // // geo_point_t test_p(10,10,10); - // // const auto& fit_seg_dpc = segment->dpcloud("main"); - // // auto closest_result = fit_seg_dpc->kd3d().knn(1, test_p); - // // double closest_3d_distance = sqrt(closest_result[0].second); - // // auto closest_2d_u = fit_seg_dpc->get_closest_2d_point_info(test_p, 0, 0, 0); - // // auto closest_2d_v = fit_seg_dpc->get_closest_2d_point_info(test_p, 1, 0, 0); - // // auto closest_2d_w = fit_seg_dpc->get_closest_2d_point_info(test_p, 2, 0, 0); - // // std::cout << closest_3d_distance << " " << std::get<0>(closest_2d_u) << " " << std::get<0>(closest_2d_v) << " " << std::get<0>(closest_2d_w) << std::endl; - // // std::cout << std::get<2>(closest_2d_u) << " " << std::get<2>(closest_2d_v) << " " << std::get<2>(closest_2d_w) << std::endl; - - // m_track_fitter.add_segment(segment); - // m_track_fitter.do_single_tracking(segment, true, true, false, true); - // // Extract fit results from the segment - // const auto& fits = segment->fits(); - - // // Print position, dQ, and dx for each fit point - // std::cout << "Fit results for " << fits.size() << " points:" << std::endl; - // for (size_t i = 0; i < fits.size(); ++i) { - // const auto& fit = fits[i]; - // std::cout << " Point " << i << ": position=(" - // << fit.point.x()/units::cm << ", " << fit.point.y()/units::cm << ", " << fit.point.z()/units::cm - // << "), dQ=" << fit.dQ << ", dx=" << fit.dx/units::cm << std::endl; - // } - // std::cout << std::endl; - - // std::cout << "After search other tracks" << std::endl; - // std::vector> fitted_segments; - // fitted_segments.push_back(segment); - // search_other_tracks(*main_cluster, fitted_segments); - - // std::cout << fitted_segments.size() << std::endl; - // // { - // // // Extract fit results from the segment - // // const auto& fits = fitted_segments.back()->fits(); - - // // // Print position, dQ, and dx for each fit point - // // std::cout << "Fit results for " << fits.size() << " points:" << std::endl; - // // for (size_t i = 0; i < fits.size(); ++i) { - // // const auto& fit = fits[i]; - // // std::cout << " Point " << i << ": position=(" - // // << fit.point.x()/units::cm << ", " << fit.point.y()/units::cm << ", " << fit.point.z()/units::cm - // // << "), dQ=" << fit.dQ << ", dx=" << fit.dx/units::cm << std::endl; - // // } - // // std::cout << std::endl; - // // } - // bool flag_other_tracks = check_other_tracks(*main_cluster, fitted_segments); - // std::cout << "Check other Tracks: " << flag_other_tracks << std::endl; - - // bool flag_other_clusters = check_other_clusters(*main_cluster, main_to_associated[main_cluster]); - // std::cout << "Check other Clusters: " << flag_other_clusters << std::endl; - - // geo_point_t mid_point(0,0,0); - // auto adjusted_path_points = adjust_rough_path(*main_cluster, mid_point); - // std::cout << "Adjust path " << mid_point << std::endl; - - // int kink_num = find_first_kink(segment); - // std::cout << "Kink " << kink_num << std::endl; - - // bool flag_proton = detect_proton(segment, kink_num, fitted_segments); - // std::cout << "Proton " << flag_proton << std::endl; - - // bool flag_eval_stm = eval_stm(segment, kink_num, 5*units::cm, 0., 35*units::cm, true); - // std::cout << "eval_stm " << flag_eval_stm << std::endl; + // validation check ... temporary ... + { + auto boundary_indices = main_cluster->get_two_boundary_steiner_graph_idx("steiner_graph", "steiner_pc", true); - // } + const auto& steiner_pc = main_cluster->get_pc("steiner_pc"); + const auto& coords = main_cluster->get_default_scope().coords; + const auto& x_coords = steiner_pc.get(coords.at(0))->elements(); + const auto& y_coords = steiner_pc.get(coords.at(1))->elements(); + const auto& z_coords = steiner_pc.get(coords.at(2))->elements(); - bool flag_stm = check_stm_conditions(*main_cluster, main_to_associated[main_cluster] ); - std::cout << "STM tagger: " << " " << flag_stm << std::endl; - if (flag_stm) { - main_cluster->set_flag(Flags::STM); - stm_count++; - } + // Add the two boundary points as additional extreme point groups + geo_point_t boundary_point_first(x_coords[boundary_indices.first], + y_coords[boundary_indices.first], + z_coords[boundary_indices.first]); + geo_point_t boundary_point_second(x_coords[boundary_indices.second], + y_coords[boundary_indices.second], + z_coords[boundary_indices.second]); + geo_point_t first_wcp = boundary_point_first; + geo_point_t last_wcp = boundary_point_second; + + std::cout << "End Points: " << first_wcp << " " << last_wcp << std::endl; + // last_wcp = geo_point_t(215.532, -95.1674, 211.193); + + auto path_points = do_rough_path(*main_cluster, first_wcp, last_wcp); + + // Create segment for tracking + auto segment = create_segment_for_cluster(*main_cluster, path_points); + + // geo_point_t test_p(10,10,10); + // const auto& fit_seg_dpc = segment->dpcloud("main"); + // auto closest_result = fit_seg_dpc->kd3d().knn(1, test_p); + // double closest_3d_distance = sqrt(closest_result[0].second); + // auto closest_2d_u = fit_seg_dpc->get_closest_2d_point_info(test_p, 0, 0, 0); + // auto closest_2d_v = fit_seg_dpc->get_closest_2d_point_info(test_p, 1, 0, 0); + // auto closest_2d_w = fit_seg_dpc->get_closest_2d_point_info(test_p, 2, 0, 0); + // std::cout << closest_3d_distance << " " << std::get<0>(closest_2d_u) << " " << std::get<0>(closest_2d_v) << " " << std::get<0>(closest_2d_w) << std::endl; + // std::cout << std::get<2>(closest_2d_u) << " " << std::get<2>(closest_2d_v) << " " << std::get<2>(closest_2d_w) << std::endl; + + m_track_fitter.add_segment(segment); + m_track_fitter.do_single_tracking(segment, true, true, false, true); + // Extract fit results from the segment + const auto& fits = segment->fits(); + + // Print position, dQ, and dx for each fit point + std::cout << "Fit results for " << fits.size() << " points:" << std::endl; + for (size_t i = 0; i < fits.size(); ++i) { + const auto& fit = fits[i]; + std::cout << " Point " << i << ": position=(" + << fit.point.x()/units::cm << ", " << fit.point.y()/units::cm << ", " << fit.point.z()/units::cm + << "), dQ=" << fit.dQ << ", dx=" << fit.dx/units::cm << std::endl; + } + std::cout << std::endl; + + + // test point cloud fit + create_segment_fit_point_cloud(segment, m_dv, "fit"); + + // Now access it directly from the segment + auto fit_dpcloud = segment->dpcloud("fit"); // Get the DynamicPointCloud + + if (fit_dpcloud) { + const auto& points = fit_dpcloud->get_points(); + + std::cout << "Fit point cloud has " << points.size() << " points:" << std::endl; + // for (size_t i = 0; i < points.size(); ++i) { + // std::cout << " Point " << i << ": (" + // << points[i].x/units::cm << ", " + // << points[i].y/units::cm << ", " + // << points[i].z/units::cm << ") cm" << std::endl; + // } + + // // You can also verify against the segment's fit data + // const auto& fits = segment->fits(); + // std::cout << "\nVerification:" << std::endl; + // std::cout << " Point cloud size: " << points.size() << std::endl; + // std::cout << " Segment fits size: " << fits.size() << std::endl; + + // if (points.size() == fits.size()) { + // std::cout << " ✓ Sizes match!" << std::endl; + // } + } else { + std::cout << "Fit point cloud not found on segment!" << std::endl; + } + + + // std::cout << "After search other tracks" << std::endl; + // std::vector> fitted_segments; + // fitted_segments.push_back(segment); + // search_other_tracks(*main_cluster, fitted_segments); + + // std::cout << fitted_segments.size() << std::endl; + // // { + // // // Extract fit results from the segment + // // const auto& fits = fitted_segments.back()->fits(); + + // // // Print position, dQ, and dx for each fit point + // // std::cout << "Fit results for " << fits.size() << " points:" << std::endl; + // // for (size_t i = 0; i < fits.size(); ++i) { + // // const auto& fit = fits[i]; + // // std::cout << " Point " << i << ": position=(" + // // << fit.point.x()/units::cm << ", " << fit.point.y()/units::cm << ", " << fit.point.z()/units::cm + // // << "), dQ=" << fit.dQ << ", dx=" << fit.dx/units::cm << std::endl; + // // } + // // std::cout << std::endl; + // // } + // bool flag_other_tracks = check_other_tracks(*main_cluster, fitted_segments); + // std::cout << "Check other Tracks: " << flag_other_tracks << std::endl; + + // bool flag_other_clusters = check_other_clusters(*main_cluster, main_to_associated[main_cluster]); + // std::cout << "Check other Clusters: " << flag_other_clusters << std::endl; + + // geo_point_t mid_point(0,0,0); + // auto adjusted_path_points = adjust_rough_path(*main_cluster, mid_point); + // std::cout << "Adjust path " << mid_point << std::endl; + + // int kink_num = find_first_kink(segment); + // std::cout << "Kink " << kink_num << std::endl; + + // bool flag_proton = detect_proton(segment, kink_num, fitted_segments); + // std::cout << "Proton " << flag_proton << std::endl; + + // bool flag_eval_stm = eval_stm(segment, kink_num, 5*units::cm, 0., 35*units::cm, true); + // std::cout << "eval_stm " << flag_eval_stm << std::endl; + + } + + // bool flag_stm = check_stm_conditions(*main_cluster, main_to_associated[main_cluster] ); + // std::cout << "STM tagger: " << " " << flag_stm << std::endl; + // if (flag_stm) { + // main_cluster->set_flag(Flags::STM); + // stm_count++; + // } - (void)stm_count; + // (void)stm_count; - // hack ... - { - auto segs = m_track_fitter.get_segments(); - clustering_points_segments(segs,m_dv); - } + // // hack ... + // { + // auto segs = m_track_fitter.get_segments(); + // // std::cout << "Xin: " << segs.size() << std::endl; + // clustering_points_segments(segs,m_dv); + + // // Get the last segment from the set + // if (!segs.empty()) { + // auto segment = *segs.rbegin(); // Get last element from set + + // // test point cloud fit + // create_segment_fit_point_cloud(segment, m_dv, "fit"); + + // // Now access it directly from the segment + // auto fit_dpcloud = segment->dpcloud("fit"); // Get the DynamicPointCloud + + // // if (fit_dpcloud) { + // // const auto& points = fit_dpcloud->get_points(); + + // // std::cout << "Fit point cloud has " << points.size() << " points:" << std::endl; + // // for (size_t i = 0; i < points.size(); ++i) { + // // std::cout << " Point " << i << ": (" + // // << points[i].x/units::cm << ", " + // // << points[i].y/units::cm << ", " + // // << points[i].z/units::cm << ") cm" << std::endl; + // // } + + // // // You can also verify against the segment's fit data + // // const auto& fits = segment->fits(); + // // std::cout << "\nVerification:" << std::endl; + // // std::cout << " Point cloud size: " << points.size() << std::endl; + // // std::cout << " Segment fits size: " << fits.size() << std::endl; + + // // if (points.size() == fits.size()) { + // // std::cout << " ✓ Sizes match!" << std::endl; + // // } + // // } else { + // // std::cout << "Fit point cloud not found on segment!" << std::endl; + // // } + // } + + // } } From a457e9e9e69271353bc7d386bd24853b85a799e7 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Tue, 28 Oct 2025 08:48:56 -0700 Subject: [PATCH 002/111] continue validation --- clus/src/TaggerCheckSTM.cxx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/clus/src/TaggerCheckSTM.cxx b/clus/src/TaggerCheckSTM.cxx index 9ec741db..b559f9cd 100644 --- a/clus/src/TaggerCheckSTM.cxx +++ b/clus/src/TaggerCheckSTM.cxx @@ -209,6 +209,11 @@ class TaggerCheckSTM : public IConfigurable, public Clus::IEnsembleVisitor, priv std::cout << "Fit point cloud not found on segment!" << std::endl; } + std::cout << "Direct Length: " << segment_track_direct_length(segment) / units::cm << " cm; " << segment_track_direct_length(segment, 0, 10) / units::cm << " cm" << " " << segment_track_direct_length(segment, -1, -1, WireCell::Vector(1,0,0)) / units::cm << " cm" << std::endl; + + std::cout << "Segment Length: " << segment_track_length(segment) / units::cm << " cm; " << segment_track_length(segment,0, 0, 10) / units::cm << " cm" << " " << segment_track_length(segment,0, -1, -1, WireCell::Vector(1,0,0)) / units::cm << " cm" << std::endl; + std::cout << "Max deviation: " << segment_track_max_deviation(segment) / units::cm << " cm; " << segment_track_max_deviation(segment, 0, 10) / units::cm << " cm" << std::endl; + // std::cout << "After search other tracks" << std::endl; // std::vector> fitted_segments; From eb3190d267705477fca100e46bf07750782561c1 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Tue, 28 Oct 2025 08:58:46 -0700 Subject: [PATCH 003/111] continue validation --- clus/src/TaggerCheckSTM.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clus/src/TaggerCheckSTM.cxx b/clus/src/TaggerCheckSTM.cxx index b559f9cd..3fa18b60 100644 --- a/clus/src/TaggerCheckSTM.cxx +++ b/clus/src/TaggerCheckSTM.cxx @@ -213,7 +213,7 @@ class TaggerCheckSTM : public IConfigurable, public Clus::IEnsembleVisitor, priv std::cout << "Segment Length: " << segment_track_length(segment) / units::cm << " cm; " << segment_track_length(segment,0, 0, 10) / units::cm << " cm" << " " << segment_track_length(segment,0, -1, -1, WireCell::Vector(1,0,0)) / units::cm << " cm" << std::endl; std::cout << "Max deviation: " << segment_track_max_deviation(segment) / units::cm << " cm; " << segment_track_max_deviation(segment, 0, 10) / units::cm << " cm" << std::endl; - + std::cout << "dQ_dx: " << segment_rms_dQ_dx(segment) << " " << segment_mean_dQ_dx(segment) << " " << std::endl; // std::cout << "After search other tracks" << std::endl; // std::vector> fitted_segments; From c7021b212e0d1143d3a3894fe4932cc090161308 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Tue, 28 Oct 2025 09:13:04 -0700 Subject: [PATCH 004/111] continue validation --- clus/inc/WireCellClus/PRSegmentFunctions.h | 2 +- clus/src/PRSegmentFunctions.cxx | 24 +++++++++++++++++----- clus/src/TaggerCheckSTM.cxx | 2 +- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/clus/inc/WireCellClus/PRSegmentFunctions.h b/clus/inc/WireCellClus/PRSegmentFunctions.h index 631cb6a7..cd06ff76 100644 --- a/clus/inc/WireCellClus/PRSegmentFunctions.h +++ b/clus/inc/WireCellClus/PRSegmentFunctions.h @@ -61,7 +61,7 @@ namespace WireCell::Clus::PR { /// /// @param seg The segment containing fit data /// @return Median dQ/dx value (0 if no valid fits) - double segment_median_dQ_dx(SegmentPtr seg); + double segment_median_dQ_dx(SegmentPtr seg, int n1 = -1, int n2 = -1); double segment_rms_dQ_dx(SegmentPtr seg); diff --git a/clus/src/PRSegmentFunctions.cxx b/clus/src/PRSegmentFunctions.cxx index ba4dcdd3..ee7b0815 100644 --- a/clus/src/PRSegmentFunctions.cxx +++ b/clus/src/PRSegmentFunctions.cxx @@ -611,19 +611,33 @@ namespace WireCell::Clus::PR { - double segment_median_dQ_dx(SegmentPtr seg) + double segment_median_dQ_dx(SegmentPtr seg, int n1, int n2) { auto& fits = seg->fits(); if (fits.empty()) { return 0.0; } + // Handle default parameters (equivalent to get_medium_dQ_dx()) + if (n1 < 0 && n2 < 0) { + n1 = 0; + n2 = static_cast(fits.size()); + } + + // Clamp indices to valid range (equivalent to WCPPID bounds checking) + if (n1 < 0) n1 = 0; + if (n1 + 1 > static_cast(fits.size())) n1 = static_cast(fits.size()) - 1; + if (n2 < 0) n2 = 0; + if (n2 + 1 > static_cast(fits.size())) n2 = static_cast(fits.size()) - 1; + std::vector vec_dQ_dx; - vec_dQ_dx.reserve(fits.size()); + vec_dQ_dx.reserve(n2 - n1 + 1); - for (auto& fit : fits) { + // Loop over specified range [n1, n2] (inclusive, matching WCPPID) + for (int i = n1; i <= n2 && i < static_cast(fits.size()); i++) { + auto& fit = fits[i]; if (fit.valid() && fit.dx > 0 && fit.dQ >= 0) { - // Add small epsilon to avoid division by zero (same as original) + // Add small epsilon to avoid division by zero (same as WCPPID: 1e-9) vec_dQ_dx.push_back(fit.dQ / (fit.dx + 1e-9)); } } @@ -632,7 +646,7 @@ namespace WireCell::Clus::PR { return 0.0; } - // Use nth_element to find median (same algorithm as original) + // Use nth_element to find median (exact WCPPID algorithm) size_t median_index = vec_dQ_dx.size() / 2; std::nth_element(vec_dQ_dx.begin(), vec_dQ_dx.begin() + median_index, diff --git a/clus/src/TaggerCheckSTM.cxx b/clus/src/TaggerCheckSTM.cxx index 3fa18b60..3bbc6d42 100644 --- a/clus/src/TaggerCheckSTM.cxx +++ b/clus/src/TaggerCheckSTM.cxx @@ -213,7 +213,7 @@ class TaggerCheckSTM : public IConfigurable, public Clus::IEnsembleVisitor, priv std::cout << "Segment Length: " << segment_track_length(segment) / units::cm << " cm; " << segment_track_length(segment,0, 0, 10) / units::cm << " cm" << " " << segment_track_length(segment,0, -1, -1, WireCell::Vector(1,0,0)) / units::cm << " cm" << std::endl; std::cout << "Max deviation: " << segment_track_max_deviation(segment) / units::cm << " cm; " << segment_track_max_deviation(segment, 0, 10) / units::cm << " cm" << std::endl; - std::cout << "dQ_dx: " << segment_rms_dQ_dx(segment) << " " << segment_mean_dQ_dx(segment) << " " << std::endl; + std::cout << "dQ_dx: " << segment_rms_dQ_dx(segment) << " " << segment_median_dQ_dx(segment) << " " << segment_median_dQ_dx(segment,0,10) << std::endl; // std::cout << "After search other tracks" << std::endl; // std::vector> fitted_segments; From 8211d9b5ba9431ecd6f2d7465abb96208cebe00a Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Tue, 28 Oct 2025 11:05:26 -0700 Subject: [PATCH 005/111] continue validation --- clus/src/PRSegmentFunctions.cxx | 14 +++++++++++--- clus/src/TaggerCheckSTM.cxx | 5 +++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/clus/src/PRSegmentFunctions.cxx b/clus/src/PRSegmentFunctions.cxx index ee7b0815..3a8af258 100644 --- a/clus/src/PRSegmentFunctions.cxx +++ b/clus/src/PRSegmentFunctions.cxx @@ -906,9 +906,17 @@ namespace WireCell::Clus::PR { } } else if (direction == -1) { // Backward direction - for (int i = start; i < start + num_points - 1 && (fits.size() - i - 1) < fits.size(); i++) { - if (fits.size() - start < fits.size()) { - p = p + (fits[fits.size() - i - 1].point - fits[fits.size() - start].point); + for (int i = start; i < start + num_points - 1; i++) { + // WCPPID's bounds check + if (i + 1 > static_cast(fits.size())) break; + + // Ensure backward indices are valid + int back_idx = fits.size() - i - 1; + int ref_idx = fits.size() - start; + + if (back_idx >= 0 && back_idx < static_cast(fits.size()) && + ref_idx >= 0 && ref_idx < static_cast(fits.size())) { + p = p + (fits[back_idx].point - fits[ref_idx].point); } } } diff --git a/clus/src/TaggerCheckSTM.cxx b/clus/src/TaggerCheckSTM.cxx index 3bbc6d42..c63666e8 100644 --- a/clus/src/TaggerCheckSTM.cxx +++ b/clus/src/TaggerCheckSTM.cxx @@ -215,6 +215,11 @@ class TaggerCheckSTM : public IConfigurable, public Clus::IEnsembleVisitor, priv std::cout << "Max deviation: " << segment_track_max_deviation(segment) / units::cm << " cm; " << segment_track_max_deviation(segment, 0, 10) / units::cm << " cm" << std::endl; std::cout << "dQ_dx: " << segment_rms_dQ_dx(segment) << " " << segment_median_dQ_dx(segment) << " " << segment_median_dQ_dx(segment,0,10) << std::endl; + auto kink_results = segment_search_kink(segment, first_wcp, "fit"); + std::cout <<"Kink search: " << std::get<0>(kink_results) << " " << std::get<1>(kink_results) << " " << std::get<2>(kink_results) << " " << std::get<3>(kink_results) <> fitted_segments; // fitted_segments.push_back(segment); From 2ced9782e1d01b40689eda4e9087b8e7396ede34 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Tue, 28 Oct 2025 12:33:32 -0700 Subject: [PATCH 006/111] fix a bug in the Box Recombination Model --- clus/src/PRSegmentFunctions.cxx | 4 ++++ clus/src/TaggerCheckSTM.cxx | 38 ++++++++++++++++++++++++++++++++- gen/src/RecombinationModels.cxx | 14 +++++++----- 3 files changed, 50 insertions(+), 6 deletions(-) diff --git a/clus/src/PRSegmentFunctions.cxx b/clus/src/PRSegmentFunctions.cxx index 3a8af258..f757a298 100644 --- a/clus/src/PRSegmentFunctions.cxx +++ b/clus/src/PRSegmentFunctions.cxx @@ -996,6 +996,10 @@ namespace WireCell::Clus::PR { // Calculate dE/dx using Box model inverse formula from original code double dE = recomb_model->dE(dQ, dx); + + double dQp = (*recomb_model)(dE, dx); + + std::cout << dQ << " " << dx << " " << dE << " " << units::MeV << " " << dQp << std::endl; // Apply bounds (same as original) if (dE < 0) dE = 0; diff --git a/clus/src/TaggerCheckSTM.cxx b/clus/src/TaggerCheckSTM.cxx index c63666e8..9e3835a5 100644 --- a/clus/src/TaggerCheckSTM.cxx +++ b/clus/src/TaggerCheckSTM.cxx @@ -167,7 +167,7 @@ class TaggerCheckSTM : public IConfigurable, public Clus::IEnsembleVisitor, priv m_track_fitter.do_single_tracking(segment, true, true, false, true); // Extract fit results from the segment const auto& fits = segment->fits(); - + std::vector vec_dQ, vec_dx; // Print position, dQ, and dx for each fit point std::cout << "Fit results for " << fits.size() << " points:" << std::endl; for (size_t i = 0; i < fits.size(); ++i) { @@ -175,6 +175,8 @@ class TaggerCheckSTM : public IConfigurable, public Clus::IEnsembleVisitor, priv std::cout << " Point " << i << ": position=(" << fit.point.x()/units::cm << ", " << fit.point.y()/units::cm << ", " << fit.point.z()/units::cm << "), dQ=" << fit.dQ << ", dx=" << fit.dx/units::cm << std::endl; + vec_dQ.push_back(fit.dQ); + vec_dx.push_back(fit.dx); } std::cout << std::endl; @@ -220,6 +222,40 @@ class TaggerCheckSTM : public IConfigurable, public Clus::IEnsembleVisitor, priv std::cout <<"Shower Trajectory: " << segment_is_shower_trajectory(segment) << std::endl; std::cout <<"3D Vector: " << segment_cal_dir_3vector(segment) << " " << segment_cal_dir_3vector(segment, last_wcp, 10*units::cm) << " " << segment_cal_dir_3vector(segment, -1, 10, 1) << std::endl; + vec_dQ.clear(); + vec_dx.clear(); + + vec_dQ.push_back(35750.7); vec_dx.push_back(0.591606 * units::cm); + vec_dQ.push_back(32381.5); vec_dx.push_back(0.532785 * units::cm); + vec_dQ.push_back(30075.9); vec_dx.push_back(0.482393 * units::cm); + vec_dQ.push_back(32805.1); vec_dx.push_back(0.49908 * units::cm); + vec_dQ.push_back(46702.9); vec_dx.push_back(0.664835 * units::cm); + vec_dQ.push_back(58132.3); vec_dx.push_back(0.779598 * units::cm); + vec_dQ.push_back(58407.1); vec_dx.push_back(0.759001 * units::cm); + vec_dQ.push_back(55774.6); vec_dx.push_back(0.767953 * units::cm); + vec_dQ.push_back(51256.7); vec_dx.push_back(0.746227 * units::cm); + vec_dQ.push_back(42653.3); vec_dx.push_back(0.607304 * units::cm); + vec_dQ.push_back(47765.7); vec_dx.push_back(0.644757 * units::cm); + vec_dQ.push_back(55210.0); vec_dx.push_back(0.726355 * units::cm); + vec_dQ.push_back(44971.7); vec_dx.push_back(0.624519 * units::cm); + vec_dQ.push_back(35688.0); vec_dx.push_back(0.541333 * units::cm); + vec_dQ.push_back(37316.4); vec_dx.push_back(0.613396 * units::cm); + vec_dQ.push_back(37136.7); vec_dx.push_back(0.643779 * units::cm); + vec_dQ.push_back(33273.7); vec_dx.push_back(0.544746 * units::cm); + vec_dQ.push_back(32636.5); vec_dx.push_back(0.546531 * units::cm); + vec_dQ.push_back(35736.8); vec_dx.push_back(0.634489 * units::cm); + vec_dQ.push_back(35515.5); vec_dx.push_back(0.620087 * units::cm); + vec_dQ.push_back(36371.2); vec_dx.push_back(0.657168 * units::cm); + vec_dQ.push_back(37250.7); vec_dx.push_back(0.78021 * units::cm); + vec_dQ.push_back(29661.0); vec_dx.push_back(0.648785 * units::cm); + vec_dQ.push_back(27046.6); vec_dx.push_back(0.578585 * units::cm); + vec_dQ.push_back(28468.3); vec_dx.push_back(0.611002 * units::cm); + vec_dQ.push_back(33398.8); vec_dx.push_back(0.685772 * units::cm); + vec_dQ.push_back(42891.4); vec_dx.push_back(0.714633 * units::cm); + vec_dQ.push_back(44924.0); vec_dx.push_back(0.628143 * units::cm); + + std::cout <<"Kine dQ_dx: " << segment_cal_kine_dQdx(segment, m_recomb_model) << " " << WireCell::Clus::PR::cal_kine_dQdx(vec_dQ, vec_dx, m_recomb_model) << " " << WireCell::Clus::PR::cal_kine_range(segment_track_length(segment), 13, particle_data()) << std::endl; + // std::cout << "After search other tracks" << std::endl; // std::vector> fitted_segments; // fitted_segments.push_back(segment); diff --git a/gen/src/RecombinationModels.cxx b/gen/src/RecombinationModels.cxx index 233150c4..38dd3186 100644 --- a/gen/src/RecombinationModels.cxx +++ b/gen/src/RecombinationModels.cxx @@ -6,9 +6,9 @@ WIRECELL_FACTORY(MipRecombination, WireCell::Gen::MipRecombination, WireCell::IRecombinationModel, WireCell::IConfigurable) -WIRECELL_FACTORY(BirksRecombination, WireCell::Gen::MipRecombination, WireCell::IRecombinationModel, +WIRECELL_FACTORY(BirksRecombination, WireCell::Gen::BirksRecombination, WireCell::IRecombinationModel, WireCell::IConfigurable) -WIRECELL_FACTORY(BoxRecombination, WireCell::Gen::MipRecombination, WireCell::IRecombinationModel, +WIRECELL_FACTORY(BoxRecombination, WireCell::Gen::BoxRecombination, WireCell::IRecombinationModel, WireCell::IConfigurable) using namespace WireCell; @@ -58,6 +58,7 @@ double Gen::BirksRecombination::dE(double dQ, double dX) { const double numerator = dQ; const double denominator = m_a3t/m_wi - dQ/dX * m_k3t/(m_efield*m_rho); + return numerator / denominator; } void Gen::BirksRecombination::configure(const WireCell::Configuration& config) @@ -93,16 +94,19 @@ Gen::BoxRecombination::BoxRecombination(double Efield, double A, double B, doubl Gen::BoxRecombination::~BoxRecombination() {} double Gen::BoxRecombination::operator()(double dE, double dX) { - const double tmp = (dE / dX) * m_b / (m_efield * m_rho); + const double tmp = (dE /units::MeV*units::cm/ dX) * m_b / (m_efield * m_rho); const double R = std::log(m_a + tmp) / tmp; return R * dE / m_wi; } double Gen::BoxRecombination::dE(double dQ, double dX) { const double coeff = m_b / (m_efield * m_rho); - const double a_exp = std::exp(dQ/dX * coeff * m_wi); - const double numerator = (a_exp - m_a)*dX; + const double a_exp = std::exp(dQ/dX*units::cm * coeff * m_wi); + const double numerator = (a_exp - m_a) * units::MeV/units::cm *dX; const double denominator = coeff; + + // std::cout << "Test: " << m_a << " " << m_b << " " << coeff << " " << a_exp << " " << numerator << " " << denominator << std::endl; + return numerator / denominator; } void Gen::BoxRecombination::configure(const WireCell::Configuration& config) From 277fd15f1bf0f55b4a8234f5aab380f3b3f06d3a Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Tue, 28 Oct 2025 12:35:10 -0700 Subject: [PATCH 007/111] fix a bug in the Box Recombination Model --- gen/src/RecombinationModels.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gen/src/RecombinationModels.cxx b/gen/src/RecombinationModels.cxx index 38dd3186..5da6135f 100644 --- a/gen/src/RecombinationModels.cxx +++ b/gen/src/RecombinationModels.cxx @@ -51,13 +51,13 @@ Gen::BirksRecombination::BirksRecombination(double Efield, double A3t, double k3 Gen::BirksRecombination::~BirksRecombination() {} double Gen::BirksRecombination::operator()(double dE, double dX) { - const double R = m_a3t / (1 + (dE / dX) * m_k3t / (m_efield * m_rho)); + const double R = m_a3t / (1 + (dE * units::cm / dX) * m_k3t / (m_efield * m_rho)); return R * dE / m_wi; } double Gen::BirksRecombination::dE(double dQ, double dX) { const double numerator = dQ; - const double denominator = m_a3t/m_wi - dQ/dX * m_k3t/(m_efield*m_rho); + const double denominator = m_a3t/m_wi - dQ/dX*units::cm * m_k3t/(m_efield*m_rho); return numerator / denominator; } From 122ad0ba1187965a18cab0e1bf8d5a0abeb9773b Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Tue, 28 Oct 2025 14:21:27 -0700 Subject: [PATCH 008/111] fix a bug --- clus/src/PRSegmentFunctions.cxx | 13 +++++++------ clus/src/TaggerCheckSTM.cxx | 13 +++++++++++++ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/clus/src/PRSegmentFunctions.cxx b/clus/src/PRSegmentFunctions.cxx index f757a298..ca33dc01 100644 --- a/clus/src/PRSegmentFunctions.cxx +++ b/clus/src/PRSegmentFunctions.cxx @@ -997,9 +997,9 @@ namespace WireCell::Clus::PR { // Calculate dE/dx using Box model inverse formula from original code double dE = recomb_model->dE(dQ, dx); - double dQp = (*recomb_model)(dE, dx); + // double dQp = (*recomb_model)(dE, dx); - std::cout << dQ << " " << dx << " " << dE << " " << units::MeV << " " << dQp << std::endl; + // std::cout << dQ << " " << dx << " " << dE << " " << units::MeV << " " << dQp << std::endl; // Apply bounds (same as original) if (dE < 0) dE = 0; @@ -1036,8 +1036,8 @@ namespace WireCell::Clus::PR { for (size_t i = 0; i != ncount; i++) { muon_ref[i] = particle_data->get_dEdx_function("muon")->scalar_function((vec_x[i])/units::cm) /units::cm; - proton_ref[i] = particle_data->get_dEdx_function("proton")->scalar_function((vec_x[i])/units::cm)/ units::cm; - electron_ref[i] = particle_data->get_dEdx_function("electron")->scalar_function((vec_x[i])/units::cm)/ units::cm; + proton_ref[i] = particle_data->get_dEdx_function("proton")->scalar_function((vec_x[i])/units::cm) / units::cm; + electron_ref[i] = particle_data->get_dEdx_function("electron")->scalar_function((vec_x[i])/units::cm) / units::cm; } // Perform KS-like tests using kslike_compare @@ -1057,6 +1057,8 @@ namespace WireCell::Clus::PR { double ratio4 = std::accumulate(electron_ref.begin(), electron_ref.end(), 0.0) / (std::accumulate(vec_y.begin(), vec_y.end(), 0.0) + 1e-9); + // std::cout << ks1 << " " << ratio1 << " " << ks2 << " " << ratio2 << " " << ks3 << " " << ratio3 << " " << ks4 << " " << ratio4 << std::endl; + std::vector results; // Convert bool result to double (1.0 for true, 0.0 for false) results.push_back(eval_ks_ratio(ks1, ks2, ratio1, ratio2) ? 1.0 : 0.0); // direction metric @@ -1089,9 +1091,8 @@ namespace WireCell::Clus::PR { if (!range_function) { // Default to muon if particle type not recognized - range_function = particle_data->get_range_function("muon"); + range_function = particle_data->get_range_function("muon"); } - double kine_energy = range_function->scalar_function(L/units::cm) * units::MeV; return kine_energy; } diff --git a/clus/src/TaggerCheckSTM.cxx b/clus/src/TaggerCheckSTM.cxx index 9e3835a5..bfa9a445 100644 --- a/clus/src/TaggerCheckSTM.cxx +++ b/clus/src/TaggerCheckSTM.cxx @@ -256,6 +256,19 @@ class TaggerCheckSTM : public IConfigurable, public Clus::IEnsembleVisitor, priv std::cout <<"Kine dQ_dx: " << segment_cal_kine_dQdx(segment, m_recomb_model) << " " << WireCell::Clus::PR::cal_kine_dQdx(vec_dQ, vec_dx, m_recomb_model) << " " << WireCell::Clus::PR::cal_kine_range(segment_track_length(segment), 13, particle_data()) << std::endl; + std::vector L, dQ_dx; + for (size_t i = 0; i < vec_dQ.size(); ++i) { + if (i==0){ + L.push_back(0.); + }else{ + L.push_back(L.back() + vec_dx.at(i)); + } + dQ_dx.push_back(vec_dQ.at(i)/vec_dx.at(i)); // convert to per cm + } + auto pid_results = WireCell::Clus::PR::do_track_comp(L, dQ_dx, 35*units::cm, 0*units::cm, particle_data()); + std::cout << "Particle ID results: " << pid_results.at(0) << " " << pid_results.at(1) << " " << pid_results.at(2) << " " << pid_results.at(3) << std::endl; + + // std::cout << "After search other tracks" << std::endl; // std::vector> fitted_segments; // fitted_segments.push_back(segment); From 6a446b499c3373b302595e0b962e05ba55a6bc9d Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Tue, 28 Oct 2025 14:56:27 -0700 Subject: [PATCH 009/111] catch up --- clus/inc/WireCellClus/PRSegmentFunctions.h | 2 +- clus/src/PRSegmentFunctions.cxx | 16 ++++++++++------ clus/src/TaggerCheckSTM.cxx | 6 ++++-- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/clus/inc/WireCellClus/PRSegmentFunctions.h b/clus/inc/WireCellClus/PRSegmentFunctions.h index cd06ff76..589e3fe5 100644 --- a/clus/inc/WireCellClus/PRSegmentFunctions.h +++ b/clus/inc/WireCellClus/PRSegmentFunctions.h @@ -88,7 +88,7 @@ namespace WireCell::Clus::PR { bool eval_ks_ratio(double ks1, double ks2, double ratio1, double ratio2); std::vector do_track_comp(std::vector& L , std::vector& dQ_dx, double compare_range, double offset_length, const Clus::ParticleDataSet::pointer& particle_data, double MIP_dQdx = 50000/units::cm); // success, flag_dir, pdg_code, particle_score - std::tuple segment_do_track_pid(SegmentPtr segment, std::vector& L , std::vector& dQ_dx, double compare_range , double offset_length, bool flag_force, const Clus::ParticleDataSet::pointer& particle_data, double MIP_dQdx = 50000/units::cm); + std::tuple segment_do_track_pid(SegmentPtr segment, std::vector& L , std::vector& dQ_dx, const Clus::ParticleDataSet::pointer& particle_data, double compare_range=35*units::cm, double offset_length = 0*units::cm, bool flag_force = false, double MIP_dQdx = 50000/units::cm); // direction calculation ... WireCell::Vector segment_cal_dir_3vector(SegmentPtr seg); diff --git a/clus/src/PRSegmentFunctions.cxx b/clus/src/PRSegmentFunctions.cxx index ca33dc01..f8e095f5 100644 --- a/clus/src/PRSegmentFunctions.cxx +++ b/clus/src/PRSegmentFunctions.cxx @@ -828,6 +828,7 @@ namespace WireCell::Clus::PR { WireCell::Vector segment_cal_dir_3vector(SegmentPtr seg){ const auto& fits = seg->fits(); + if (fits.size() < 2) { return WireCell::Vector(0, 0, 0); } @@ -1098,7 +1099,7 @@ namespace WireCell::Clus::PR { } // success, flag_dir, particle_type, particle_score - std::tuple segment_do_track_pid(SegmentPtr segment, std::vector& L , std::vector& dQ_dx, double compare_range , double offset_length, bool flag_force, const Clus::ParticleDataSet::pointer& particle_data, double MIP_dQdx){ + std::tuple segment_do_track_pid(SegmentPtr segment, std::vector& L , std::vector& dQ_dx, const Clus::ParticleDataSet::pointer& particle_data, double compare_range , double offset_length, bool flag_force, double MIP_dQdx){ if (L.size() != dQ_dx.size() || L.empty() || !segment) { return std::make_tuple(false, 0, 0, 0.0); @@ -1197,6 +1198,8 @@ namespace WireCell::Clus::PR { return std::make_tuple(true, flag_dir, particle_type, particle_score); } + segment->dirsign(flag_dir); + // Reset before return - failure case return std::make_tuple(false, 0, 0, 0.0); } @@ -1220,8 +1223,9 @@ namespace WireCell::Clus::PR { double particle_mass = particle_data->get_particle_mass(pdg_code); results[0]= kine_energy + particle_mass; - double mom = sqrt(pow(results[3],2) - pow(particle_mass,2)); + double mom = sqrt(pow(results[0],2) - pow(particle_mass,2)); auto v1 = segment_cal_dir_3vector(segment); + results[1] = mom * v1.x(); results[2] = mom * v1.y(); results[3] = mom * v1.z(); @@ -1275,7 +1279,7 @@ namespace WireCell::Clus::PR { if (start_n == 1 && end_n == 1 && npoints >= 15) { // Can use the dQ/dx to do PID and direction - auto result = segment_do_track_pid(segment, L, dQ_dx, 35*units::cm, 1*units::cm, true, particle_data); + auto result = segment_do_track_pid(segment, L, dQ_dx, particle_data, 35*units::cm, 1*units::cm, true); tmp_flag_pid = std::get<0>(result); if (tmp_flag_pid) { segment->dirsign(std::get<1>(result)); @@ -1284,7 +1288,7 @@ namespace WireCell::Clus::PR { } if (!tmp_flag_pid) { - result = segment_do_track_pid(segment, L, dQ_dx, 15*units::cm, 1*units::cm, true, particle_data); + result = segment_do_track_pid(segment, L, dQ_dx, particle_data, 15*units::cm, 1*units::cm, true); tmp_flag_pid = std::get<0>(result); if (tmp_flag_pid) { segment->dirsign(std::get<1>(result)); @@ -1294,7 +1298,7 @@ namespace WireCell::Clus::PR { } } else { // Can use the dQ/dx to do PID and direction - auto result = segment_do_track_pid(segment, L, dQ_dx, 35*units::cm, 0*units::cm, false, particle_data); + auto result = segment_do_track_pid(segment, L, dQ_dx, particle_data, 35*units::cm, 0*units::cm, false); tmp_flag_pid = std::get<0>(result); if (tmp_flag_pid) { segment->dirsign(std::get<1>(result)); @@ -1303,7 +1307,7 @@ namespace WireCell::Clus::PR { } if (!tmp_flag_pid) { - result = segment_do_track_pid(segment, L, dQ_dx, 15*units::cm, 0*units::cm, false, particle_data); + result = segment_do_track_pid(segment, L, dQ_dx, particle_data, 15*units::cm, 0*units::cm, false); tmp_flag_pid = std::get<0>(result); if (tmp_flag_pid) { segment->dirsign(std::get<1>(result)); diff --git a/clus/src/TaggerCheckSTM.cxx b/clus/src/TaggerCheckSTM.cxx index bfa9a445..03f009dc 100644 --- a/clus/src/TaggerCheckSTM.cxx +++ b/clus/src/TaggerCheckSTM.cxx @@ -220,7 +220,7 @@ class TaggerCheckSTM : public IConfigurable, public Clus::IEnsembleVisitor, priv auto kink_results = segment_search_kink(segment, first_wcp, "fit"); std::cout <<"Kink search: " << std::get<0>(kink_results) << " " << std::get<1>(kink_results) << " " << std::get<2>(kink_results) << " " << std::get<3>(kink_results) <dirsign() << " " << segment_cal_dir_3vector(segment) << " " << segment_cal_dir_3vector(segment, last_wcp, 10*units::cm) << " " << segment_cal_dir_3vector(segment, -1, 10, 1) << std::endl; vec_dQ.clear(); vec_dx.clear(); @@ -267,7 +267,9 @@ class TaggerCheckSTM : public IConfigurable, public Clus::IEnsembleVisitor, priv } auto pid_results = WireCell::Clus::PR::do_track_comp(L, dQ_dx, 35*units::cm, 0*units::cm, particle_data()); std::cout << "Particle ID results: " << pid_results.at(0) << " " << pid_results.at(1) << " " << pid_results.at(2) << " " << pid_results.at(3) << std::endl; - + auto results = segment_do_track_pid(segment, L, dQ_dx, particle_data()); + std::cout << std::get<0>(results) << " " << std::get<1>(results) << " " << std::get<2>(results) << " " << std::get<3>(results) << std::endl; + std::cout << "4-momentum: " << segment_cal_4mom(segment, 22, particle_data(), m_recomb_model) << " " << segment->dirsign() << std::endl; // std::cout << "After search other tracks" << std::endl; // std::vector> fitted_segments; From 26db7740a08e0099158e2b1841f799007f42d134 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Tue, 28 Oct 2025 15:21:35 -0700 Subject: [PATCH 010/111] update logic --- clus/src/PRSegmentFunctions.cxx | 38 +++++++++++++++++++-------------- clus/src/TaggerCheckSTM.cxx | 3 +++ 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/clus/src/PRSegmentFunctions.cxx b/clus/src/PRSegmentFunctions.cxx index f8e095f5..99118ee8 100644 --- a/clus/src/PRSegmentFunctions.cxx +++ b/clus/src/PRSegmentFunctions.cxx @@ -1375,33 +1375,39 @@ namespace WireCell::Clus::PR { } // Set particle mass and calculate 4-momentum - if (pdg_code != 0) { + // Only calculate if direction points toward a free end (matching WCPPID logic) + if (pdg_code != 0 && ((segment->dirsign() == 1 && end_n == 1) || (segment->dirsign() == -1 && start_n == 1))) { // Calculate 4-momentum using the identified particle type - auto four_momentum = segment_cal_4mom(segment, pdg_code, particle_data, recomb_model); + auto four_momentum = segment_cal_4mom(segment, pdg_code, particle_data, recomb_model, MIP_dQdx); // Create ParticleInfo with the identified particle auto pinfo = std::make_shared( - pdg_code, // PDG code + pdg_code, // PDG code particle_data->get_particle_mass(pdg_code), // mass particle_data->pdg_to_name(pdg_code), // name - four_momentum // 4-momentum + four_momentum // 4-momentum (E, px, py, pz) ); - // Set additional properties if available - pinfo->set_particle_score(particle_score); // This method would need to be added - - // Store particle info in segment (this would require adding particle_info to Segment class) + // Store particle info in segment segment->particle_info(pinfo); } - if (flag_print && pdg_code != 0) { - std::cout << "Segment PID: PDG=" << pdg_code - << ", Score=" << particle_score - << ", Length=" << length / units::cm << " cm" - << ", Direction=" << segment->dirsign() - << (segment->dir_weak() ? " (weak)" : "") - << ", Medium dQ/dx=" << segment_median_dQ_dx(segment) / (MIP_dQdx) - << " MIP" + if (flag_print) { + // Match WCPPID output format: id, length, "Track", flag_dir, is_dir_weak, particle_type, mass, KE, particle_score + double particle_mass = pdg_code != 0 ? particle_data->get_particle_mass(pdg_code) : 0.0; + double kinetic_energy = 0.0; + + if (segment->has_particle_info()) { + kinetic_energy = segment->particle_info()->kinetic_energy(); + } + + std::cout << "Seg " << length/units::cm << " cm Track " + << segment->dirsign() << " " + << (segment->dir_weak() ? 1 : 0) << " " + << pdg_code << " " + << particle_mass/units::MeV << " MeV " + << kinetic_energy/units::MeV << " MeV " + << particle_score << std::endl; } } diff --git a/clus/src/TaggerCheckSTM.cxx b/clus/src/TaggerCheckSTM.cxx index 03f009dc..2e93f136 100644 --- a/clus/src/TaggerCheckSTM.cxx +++ b/clus/src/TaggerCheckSTM.cxx @@ -271,6 +271,9 @@ class TaggerCheckSTM : public IConfigurable, public Clus::IEnsembleVisitor, priv std::cout << std::get<0>(results) << " " << std::get<1>(results) << " " << std::get<2>(results) << " " << std::get<3>(results) << std::endl; std::cout << "4-momentum: " << segment_cal_4mom(segment, 22, particle_data(), m_recomb_model) << " " << segment->dirsign() << std::endl; + segment_determine_dir_track(segment, 1, 1, particle_data(), m_recomb_model, 43000/units::cm, true); + + // std::cout << "After search other tracks" << std::endl; // std::vector> fitted_segments; // fitted_segments.push_back(segment); From 9653eaf7345617f2f56d9d0c17b67a267ab8ec97 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Tue, 28 Oct 2025 15:31:30 -0700 Subject: [PATCH 011/111] update code --- clus/src/PRSegmentFunctions.cxx | 55 +++++++++++++++++++++------------ clus/src/TaggerCheckSTM.cxx | 1 + 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/clus/src/PRSegmentFunctions.cxx b/clus/src/PRSegmentFunctions.cxx index 99118ee8..1ab6ab02 100644 --- a/clus/src/PRSegmentFunctions.cxx +++ b/clus/src/PRSegmentFunctions.cxx @@ -1417,43 +1417,60 @@ namespace WireCell::Clus::PR { double length = segment_track_length(segment, 0); // hack for now ... - int pdg_code = 11; + int pdg_code = 11; // electron + double particle_score = 0.0; - if (start_n==1 && end_n >1){ + if (start_n == 1 && end_n > 1){ segment->dirsign(-1); - }else if (start_n > 1 && end_n == 1){ + } else if (start_n > 1 && end_n == 1){ segment->dirsign(1); - }else{ + } else { + // Try track PID first segment_determine_dir_track(segment, start_n, end_n, particle_data, recomb_model, MIP_dQdx, false); - if (segment->particle_info()->pdg() != 11){ + + // Check if particle info was set and if it's not an electron + if (segment->has_particle_info() && segment->particle_info()->pdg() != 11) { + // Reset to electron and no direction + pdg_code = 11; + segment->dirsign(0); + } else if (segment->has_particle_info()) { + // Keep the electron identification from track PID + pdg_code = segment->particle_info()->pdg(); + } else { + // No particle info set, default to electron with no direction + pdg_code = 11; segment->dirsign(0); } } - - auto four_momentum = segment_cal_4mom(segment, pdg_code, particle_data, recomb_model); + + // Always calculate 4-momentum for shower trajectories (matching WCPPID) + auto four_momentum = segment_cal_4mom(segment, pdg_code, particle_data, recomb_model, MIP_dQdx); // Create ParticleInfo with the identified particle auto pinfo = std::make_shared( - pdg_code, // PDG code + pdg_code, // PDG code (electron) particle_data->get_particle_mass(pdg_code), // mass particle_data->pdg_to_name(pdg_code), // name - four_momentum // 4-momentum + four_momentum // 4-momentum (E, px, py, pz) ); - // Store particle info in segment (this would require adding particle_info to Segment class) + // Store particle info in segment segment->particle_info(pinfo); - if (flag_print ) { - std::cout << "Segment PID: PDG=" << pdg_code - << ", Length=" << length / units::cm << " cm" - << ", Direction=" << segment->dirsign() - << (segment->dir_weak() ? " (weak)" : "") - << ", Medium dQ/dx=" << segment_median_dQ_dx(segment) / (MIP_dQdx) - << " MIP" + if (flag_print) { + // Match WCPPID output format: id, length, "S_traj", flag_dir, is_dir_weak, particle_type, mass, KE, particle_score + double particle_mass = particle_data->get_particle_mass(pdg_code); + double kinetic_energy = pinfo->kinetic_energy(); + + std::cout << "Seg " << length/units::cm << " cm S_traj " + << segment->dirsign() << " " + << (segment->dir_weak() ? 1 : 0) << " " + << pdg_code << " " + << particle_mass/units::MeV << " MeV " + << kinetic_energy/units::MeV << " MeV " + << particle_score << std::endl; } - - } void clustering_points_segments(std::set segments, const IDetectorVolumes::pointer& dv, const std::string& cloud_name, double search_range, double scaling_2d){ diff --git a/clus/src/TaggerCheckSTM.cxx b/clus/src/TaggerCheckSTM.cxx index 2e93f136..65dd273a 100644 --- a/clus/src/TaggerCheckSTM.cxx +++ b/clus/src/TaggerCheckSTM.cxx @@ -272,6 +272,7 @@ class TaggerCheckSTM : public IConfigurable, public Clus::IEnsembleVisitor, priv std::cout << "4-momentum: " << segment_cal_4mom(segment, 22, particle_data(), m_recomb_model) << " " << segment->dirsign() << std::endl; segment_determine_dir_track(segment, 1, 1, particle_data(), m_recomb_model, 43000/units::cm, true); + segment_determine_shower_direction_trajectory(segment, 1,1 , particle_data(), m_recomb_model, 43000/units::cm, true); // std::cout << "After search other tracks" << std::endl; From 089a471273f2cece00a430142f5a14cb5984d841 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Tue, 28 Oct 2025 16:34:21 -0700 Subject: [PATCH 012/111] implement algorithm for the break_segment_at_point --- clus/inc/WireCellClus/PRSegmentFunctions.h | 2 +- clus/src/PRSegmentFunctions.cxx | 121 +++++++++++++++++++-- 2 files changed, 114 insertions(+), 9 deletions(-) diff --git a/clus/inc/WireCellClus/PRSegmentFunctions.h b/clus/inc/WireCellClus/PRSegmentFunctions.h index 589e3fe5..b5d21aad 100644 --- a/clus/inc/WireCellClus/PRSegmentFunctions.h +++ b/clus/inc/WireCellClus/PRSegmentFunctions.h @@ -21,7 +21,7 @@ namespace WireCell::Clus::PR { /// The point must be withing max_dist of the segment. /// /// Returns true if the graph was modified. - bool break_segment(Graph& graph, SegmentPtr seg, Point point, + bool break_segment(Graph& graph, SegmentPtr seg, Point point, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model, const IDetectorVolumes::pointer& dv, double max_dist=1e9*units::cm); // patter recognition std::tuple segment_search_kink(SegmentPtr seg, WireCell::Point& start_p, const std::string& cloud_name = "fit", double dQ_dx_threshold = 43000/units::cm ); diff --git a/clus/src/PRSegmentFunctions.cxx b/clus/src/PRSegmentFunctions.cxx index 1ab6ab02..1626bb8b 100644 --- a/clus/src/PRSegmentFunctions.cxx +++ b/clus/src/PRSegmentFunctions.cxx @@ -380,7 +380,7 @@ namespace WireCell::Clus::PR { - bool break_segment(Graph& graph, SegmentPtr seg, Point point, double max_dist/*=1e9*/) + bool break_segment(Graph& graph, SegmentPtr seg, Point point, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model, const IDetectorVolumes::pointer& dv, double max_dist/*=1e9*/) { /// sanity checks if (! seg->descriptor_valid()) { @@ -429,20 +429,125 @@ namespace WireCell::Clus::PR { // fill in the new objects. All three get the middle thing - + // Split wcpts - break point included in both seg1->wcpts(std::vector(wcpts.begin(), itwcpts+1)); seg2->wcpts(std::vector(itwcpts, wcpts.end())); vtx->wcpt(*itwcpts); + // Split fits - break point included in both seg1->fits(std::vector(fits.begin(), itfits+1)); seg2->fits(std::vector(itfits, fits.end())); vtx->fit(*itfits); - //.... more for segment - // dir_weak - // flags (dir, shower traj, shower topo) - // particle type and mass and score - // points clouds + // Copy segment properties from original to both new segments (matching WCPPID) + seg1->dir_weak(seg->dir_weak()); + seg2->dir_weak(seg->dir_weak()); + + seg1->dirsign(seg->dirsign()); + seg2->dirsign(seg->dirsign()); + + // Copy all flags + seg1->flags_set(seg->flags()); + seg2->flags_set(seg->flags()); + + if (seg->has_particle_info()) { + // Copy particle info if it exists + segment_cal_4mom(seg1, seg->particle_info()->pdg(), particle_data, recomb_model); + segment_cal_4mom(seg2, seg->particle_info()->pdg(), particle_data, recomb_model); + } + + // Copy dynamic point clouds if they exist + // The dpcloud() method returns the DynamicPointCloud associated with a given name + if (seg->dpcloud("fit")) { + create_segment_fit_point_cloud(seg1, dv, "fit"); + create_segment_fit_point_cloud(seg2, dv, "fit"); + } + + if (seg->dpcloud("main")) { + // Convert WCPoint to Point for create_segment_point_cloud + const auto& wcpts1 = seg1->wcpts(); + const auto& wcpts2 = seg2->wcpts(); + std::vector points1, points2; + points1.reserve(wcpts1.size()); + points2.reserve(wcpts2.size()); + for (const auto& wcp : wcpts1) { + points1.push_back(wcp.point); + } + for (const auto& wcp : wcpts2) { + points2.push_back(wcp.point); + } + create_segment_point_cloud(seg1, points1, dv, "main"); + create_segment_point_cloud(seg2, points2, dv, "main"); + } + + if (seg->dpcloud("associate_points")) { + // Redistribute associated points based on closest distance to seg1 vs seg2 + // This matches WCPPID lines 123-140 + + auto orig_dpc = seg->dpcloud("associate_points"); + const auto& orig_points = orig_dpc->get_points(); + + // Separate points based on which segment they're closer to + std::vector points1, points2; + points1.reserve(orig_points.size() / 2); // estimate + points2.reserve(orig_points.size() / 2); + + // Determine which cloud to use for distance calculations + // Prefer "fit" points, but fall back to "main" if "fit" is not available + std::string ref_cloud_name = "fit"; + if (!seg1->dpcloud("fit") || !seg2->dpcloud("fit")) { + ref_cloud_name = "main"; + // Ensure main clouds exist for both segments + if (!seg1->dpcloud("main") || !seg2->dpcloud("main")) { + raise("break_segment: cannot redistribute associate_points - neither 'fit' nor 'main' clouds available"); + } + } + + // Iterate through all associated points + for (const auto& dpc_point : orig_points) { + WireCell::Point point(dpc_point.x, dpc_point.y, dpc_point.z); + + // Compute closest distance to seg1 and seg2 using reference cloud + auto [dist1, _1] = segment_get_closest_point(seg1, point, ref_cloud_name); + auto [dist2, _2] = segment_get_closest_point(seg2, point, ref_cloud_name); + + // Add point to closer segment + if (dist1 < dist2) { + points1.push_back(dpc_point); + } else { + points2.push_back(dpc_point); + } + } + + // Get wpid_params from the original cloud + auto& cluster = *seg->cluster(); + const auto& wpids = cluster.grouping()->wpids(); + std::map> wpid_params; + std::map> wpid_U_dir; + std::map> wpid_V_dir; + std::map> wpid_W_dir; + std::set apas; + Facade::compute_wireplane_params(wpids, dv, wpid_params, wpid_U_dir, wpid_V_dir, wpid_W_dir, apas); + + // Create new DynamicPointClouds for associated points if we have any + if (!points1.empty()) { + // Create and populate seg1's associate_points cloud + auto dpc1 = std::make_shared(wpid_params); + dpc1->add_points(points1); + seg1->dpcloud("associate_points", dpc1); + } + + if (!points2.empty()) { + // Create and populate seg2's associate_points cloud + auto dpc2 = std::make_shared(wpid_params); + dpc2->add_points(points2); + seg2->dpcloud("associate_points", dpc2); + } + + // Note: KD-tree indices are automatically rebuilt when add_points() is called + } + + return true; } @@ -1952,7 +2057,7 @@ namespace WireCell::Clus::PR { if (!dpcloud_fit) return false; // Get the associated point cloud - auto dpcloud_assoc = segment->dpcloud("associated"); + auto dpcloud_assoc = segment->dpcloud("associate_points"); if (!dpcloud_assoc) return false; const auto& assoc_points = dpcloud_assoc->get_points(); From 16da43d0ad1f3272176b2fc868dad825b20eee31 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Wed, 29 Oct 2025 07:17:05 -0700 Subject: [PATCH 013/111] continue validation --- clus/src/PRSegmentFunctions.cxx | 5 +++++ clus/src/TaggerCheckSTM.cxx | 13 +++++++++++++ 2 files changed, 18 insertions(+) diff --git a/clus/src/PRSegmentFunctions.cxx b/clus/src/PRSegmentFunctions.cxx index 1626bb8b..d8c8c81b 100644 --- a/clus/src/PRSegmentFunctions.cxx +++ b/clus/src/PRSegmentFunctions.cxx @@ -1880,6 +1880,9 @@ namespace WireCell::Clus::PR { // Determine direction based on spread analysis int flag_dir = 0; + + // std::cout << "Shower Topology Direction: " << max_spread/units::cm << " " << large_spread_length/units::cm << " " << total_effective_length/units::cm << std::endl; + // Check if this looks like a shower based on spread bool is_shower_like = ( (max_spread > 0.7*units::cm && large_spread_length > 0.2 * total_effective_length && @@ -2192,6 +2195,8 @@ namespace WireCell::Clus::PR { } } (void)max_cont_weighted_length; // Currently unused + + // std::cout << "Shower Topology Check: " << max_spread/units::cm << " " << large_spread_length/units::cm << " " << total_effective_length/units::cm << std::endl; // Determine if this is shower topology based on spread patterns if ((max_spread > 0.7*units::cm && large_spread_length > 0.2 * total_effective_length && diff --git a/clus/src/TaggerCheckSTM.cxx b/clus/src/TaggerCheckSTM.cxx index 65dd273a..461813d1 100644 --- a/clus/src/TaggerCheckSTM.cxx +++ b/clus/src/TaggerCheckSTM.cxx @@ -274,6 +274,19 @@ class TaggerCheckSTM : public IConfigurable, public Clus::IEnsembleVisitor, priv segment_determine_dir_track(segment, 1, 1, particle_data(), m_recomb_model, 43000/units::cm, true); segment_determine_shower_direction_trajectory(segment, 1,1 , particle_data(), m_recomb_model, 43000/units::cm, true); + std::set> segs; + segs.insert(segment); + clustering_points_segments(segs, m_dv, "associate_points"); + + auto associate_dpcloud = segment->dpcloud("associate_points"); // Get the DynamicPointCloud + + if (associate_dpcloud) { + const auto& points = associate_dpcloud->get_points(); + std::cout << "Fit point cloud has " << points.size() << " points: " << segment->cluster()->npoints() << std::endl; + } + std::cout << segment_is_shower_topology(segment) << " " << segment_determine_shower_direction(segment, particle_data(), m_recomb_model) << std::endl; + + // std::cout << "After search other tracks" << std::endl; // std::vector> fitted_segments; From cf5d83291cf79b2db4f94e2b8bf71130eec10afc Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Wed, 29 Oct 2025 12:43:50 -0700 Subject: [PATCH 014/111] catch up --- clus/inc/WireCellClus/PRSegmentFunctions.h | 2 +- clus/src/PRSegmentFunctions.cxx | 25 ++++- clus/src/TaggerCheckSTM.cxx | 119 +++++++++++++-------- 3 files changed, 100 insertions(+), 46 deletions(-) diff --git a/clus/inc/WireCellClus/PRSegmentFunctions.h b/clus/inc/WireCellClus/PRSegmentFunctions.h index b5d21aad..d9d00d05 100644 --- a/clus/inc/WireCellClus/PRSegmentFunctions.h +++ b/clus/inc/WireCellClus/PRSegmentFunctions.h @@ -21,7 +21,7 @@ namespace WireCell::Clus::PR { /// The point must be withing max_dist of the segment. /// /// Returns true if the graph was modified. - bool break_segment(Graph& graph, SegmentPtr seg, Point point, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model, const IDetectorVolumes::pointer& dv, + std::tuple, VertexPtr> break_segment(Graph& graph, SegmentPtr seg, Point point, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model, const IDetectorVolumes::pointer& dv, double max_dist=1e9*units::cm); // patter recognition std::tuple segment_search_kink(SegmentPtr seg, WireCell::Point& start_p, const std::string& cloud_name = "fit", double dQ_dx_threshold = 43000/units::cm ); diff --git a/clus/src/PRSegmentFunctions.cxx b/clus/src/PRSegmentFunctions.cxx index d8c8c81b..e528df7d 100644 --- a/clus/src/PRSegmentFunctions.cxx +++ b/clus/src/PRSegmentFunctions.cxx @@ -380,7 +380,7 @@ namespace WireCell::Clus::PR { - bool break_segment(Graph& graph, SegmentPtr seg, Point point, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model, const IDetectorVolumes::pointer& dv, double max_dist/*=1e9*/) + std::tuple, VertexPtr> break_segment(Graph& graph, SegmentPtr seg, Point point, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model, const IDetectorVolumes::pointer& dv, double max_dist/*=1e9*/) { /// sanity checks if (! seg->descriptor_valid()) { @@ -396,10 +396,23 @@ namespace WireCell::Clus::PR { const auto& fits = seg->fits(); auto itfits = closest_point(fits, point, owp_to_point); + + // std::cout << "Break point in system units: " << point << std::endl; + // std::cout << "Break point in cm: (" << point.x()/units::cm << ", " << point.y()/units::cm << ", " << point.z()/units::cm << ")" << std::endl; + + // for (size_t i = 0; i < fits.size(); ++i) { + // const auto& fit = fits[i]; + // double dist = (fit.point - point).magnitude(); + // std::cout << " Point " << i << ": position=(" + // << fit.point.x()/units::cm << ", " << fit.point.y()/units::cm << ", " << fit.point.z()/units::cm + // << "), dQ=" << fit.dQ << ", dx=" << fit.dx/units::cm + // << " | dist=" << dist/units::cm << " cm" << std::endl; + // } + // reject if test point is at begin or end of fits. if (itfits == fits.begin() || itfits+1 == fits.end()) { - return false; + return std::make_tuple(false, std::pair(), VertexPtr()); } const auto& wcpts = seg->wcpts(); @@ -413,6 +426,8 @@ namespace WireCell::Clus::PR { --itwcpts; } + std::cout << "Closest point found: " << itfits - fits.begin() << " / " << fits.size() << " " << itwcpts - wcpts.begin() << " / " << wcpts.size() << " points in fits\n"; + // update graph remove_segment(graph, seg); @@ -434,6 +449,9 @@ namespace WireCell::Clus::PR { seg2->wcpts(std::vector(itwcpts, wcpts.end())); vtx->wcpt(*itwcpts); + seg1->cluster(seg->cluster()); + seg2->cluster(seg->cluster()); + // Split fits - break point included in both seg1->fits(std::vector(fits.begin(), itfits+1)); seg2->fits(std::vector(itfits, fits.end())); @@ -450,6 +468,7 @@ namespace WireCell::Clus::PR { seg1->flags_set(seg->flags()); seg2->flags_set(seg->flags()); + if (seg->has_particle_info()) { // Copy particle info if it exists segment_cal_4mom(seg1, seg->particle_info()->pdg(), particle_data, recomb_model); @@ -549,7 +568,7 @@ namespace WireCell::Clus::PR { - return true; + return std::make_tuple(true,std::make_pair(seg1, seg2), vtx); } diff --git a/clus/src/TaggerCheckSTM.cxx b/clus/src/TaggerCheckSTM.cxx index 461813d1..6b6026d8 100644 --- a/clus/src/TaggerCheckSTM.cxx +++ b/clus/src/TaggerCheckSTM.cxx @@ -153,6 +153,13 @@ class TaggerCheckSTM : public IConfigurable, public Clus::IEnsembleVisitor, priv // Create segment for tracking auto segment = create_segment_for_cluster(*main_cluster, path_points); + // Create a PR::Graph with two vertices connected by this segment + // This is needed for break_segment to work properly + WireCell::Clus::PR::Graph pr_graph; + auto vtx1 = WireCell::Clus::PR::make_vertex(pr_graph); + auto vtx2 = WireCell::Clus::PR::make_vertex(pr_graph); + WireCell::Clus::PR::add_segment(pr_graph, segment, vtx1, vtx2); + // geo_point_t test_p(10,10,10); // const auto& fit_seg_dpc = segment->dpcloud("main"); // auto closest_result = fit_seg_dpc->kd3d().knn(1, test_p); @@ -222,37 +229,37 @@ class TaggerCheckSTM : public IConfigurable, public Clus::IEnsembleVisitor, priv std::cout <<"Shower Trajectory: " << segment_is_shower_trajectory(segment) << std::endl; std::cout <<"3D Vector: " << segment->dirsign() << " " << segment_cal_dir_3vector(segment) << " " << segment_cal_dir_3vector(segment, last_wcp, 10*units::cm) << " " << segment_cal_dir_3vector(segment, -1, 10, 1) << std::endl; - vec_dQ.clear(); - vec_dx.clear(); - - vec_dQ.push_back(35750.7); vec_dx.push_back(0.591606 * units::cm); - vec_dQ.push_back(32381.5); vec_dx.push_back(0.532785 * units::cm); - vec_dQ.push_back(30075.9); vec_dx.push_back(0.482393 * units::cm); - vec_dQ.push_back(32805.1); vec_dx.push_back(0.49908 * units::cm); - vec_dQ.push_back(46702.9); vec_dx.push_back(0.664835 * units::cm); - vec_dQ.push_back(58132.3); vec_dx.push_back(0.779598 * units::cm); - vec_dQ.push_back(58407.1); vec_dx.push_back(0.759001 * units::cm); - vec_dQ.push_back(55774.6); vec_dx.push_back(0.767953 * units::cm); - vec_dQ.push_back(51256.7); vec_dx.push_back(0.746227 * units::cm); - vec_dQ.push_back(42653.3); vec_dx.push_back(0.607304 * units::cm); - vec_dQ.push_back(47765.7); vec_dx.push_back(0.644757 * units::cm); - vec_dQ.push_back(55210.0); vec_dx.push_back(0.726355 * units::cm); - vec_dQ.push_back(44971.7); vec_dx.push_back(0.624519 * units::cm); - vec_dQ.push_back(35688.0); vec_dx.push_back(0.541333 * units::cm); - vec_dQ.push_back(37316.4); vec_dx.push_back(0.613396 * units::cm); - vec_dQ.push_back(37136.7); vec_dx.push_back(0.643779 * units::cm); - vec_dQ.push_back(33273.7); vec_dx.push_back(0.544746 * units::cm); - vec_dQ.push_back(32636.5); vec_dx.push_back(0.546531 * units::cm); - vec_dQ.push_back(35736.8); vec_dx.push_back(0.634489 * units::cm); - vec_dQ.push_back(35515.5); vec_dx.push_back(0.620087 * units::cm); - vec_dQ.push_back(36371.2); vec_dx.push_back(0.657168 * units::cm); - vec_dQ.push_back(37250.7); vec_dx.push_back(0.78021 * units::cm); - vec_dQ.push_back(29661.0); vec_dx.push_back(0.648785 * units::cm); - vec_dQ.push_back(27046.6); vec_dx.push_back(0.578585 * units::cm); - vec_dQ.push_back(28468.3); vec_dx.push_back(0.611002 * units::cm); - vec_dQ.push_back(33398.8); vec_dx.push_back(0.685772 * units::cm); - vec_dQ.push_back(42891.4); vec_dx.push_back(0.714633 * units::cm); - vec_dQ.push_back(44924.0); vec_dx.push_back(0.628143 * units::cm); + // vec_dQ.clear(); + // vec_dx.clear(); + + // vec_dQ.push_back(35750.7); vec_dx.push_back(0.591606 * units::cm); + // vec_dQ.push_back(32381.5); vec_dx.push_back(0.532785 * units::cm); + // vec_dQ.push_back(30075.9); vec_dx.push_back(0.482393 * units::cm); + // vec_dQ.push_back(32805.1); vec_dx.push_back(0.49908 * units::cm); + // vec_dQ.push_back(46702.9); vec_dx.push_back(0.664835 * units::cm); + // vec_dQ.push_back(58132.3); vec_dx.push_back(0.779598 * units::cm); + // vec_dQ.push_back(58407.1); vec_dx.push_back(0.759001 * units::cm); + // vec_dQ.push_back(55774.6); vec_dx.push_back(0.767953 * units::cm); + // vec_dQ.push_back(51256.7); vec_dx.push_back(0.746227 * units::cm); + // vec_dQ.push_back(42653.3); vec_dx.push_back(0.607304 * units::cm); + // vec_dQ.push_back(47765.7); vec_dx.push_back(0.644757 * units::cm); + // vec_dQ.push_back(55210.0); vec_dx.push_back(0.726355 * units::cm); + // vec_dQ.push_back(44971.7); vec_dx.push_back(0.624519 * units::cm); + // vec_dQ.push_back(35688.0); vec_dx.push_back(0.541333 * units::cm); + // vec_dQ.push_back(37316.4); vec_dx.push_back(0.613396 * units::cm); + // vec_dQ.push_back(37136.7); vec_dx.push_back(0.643779 * units::cm); + // vec_dQ.push_back(33273.7); vec_dx.push_back(0.544746 * units::cm); + // vec_dQ.push_back(32636.5); vec_dx.push_back(0.546531 * units::cm); + // vec_dQ.push_back(35736.8); vec_dx.push_back(0.634489 * units::cm); + // vec_dQ.push_back(35515.5); vec_dx.push_back(0.620087 * units::cm); + // vec_dQ.push_back(36371.2); vec_dx.push_back(0.657168 * units::cm); + // vec_dQ.push_back(37250.7); vec_dx.push_back(0.78021 * units::cm); + // vec_dQ.push_back(29661.0); vec_dx.push_back(0.648785 * units::cm); + // vec_dQ.push_back(27046.6); vec_dx.push_back(0.578585 * units::cm); + // vec_dQ.push_back(28468.3); vec_dx.push_back(0.611002 * units::cm); + // vec_dQ.push_back(33398.8); vec_dx.push_back(0.685772 * units::cm); + // vec_dQ.push_back(42891.4); vec_dx.push_back(0.714633 * units::cm); + // vec_dQ.push_back(44924.0); vec_dx.push_back(0.628143 * units::cm); std::cout <<"Kine dQ_dx: " << segment_cal_kine_dQdx(segment, m_recomb_model) << " " << WireCell::Clus::PR::cal_kine_dQdx(vec_dQ, vec_dx, m_recomb_model) << " " << WireCell::Clus::PR::cal_kine_range(segment_track_length(segment), 13, particle_data()) << std::endl; @@ -271,20 +278,48 @@ class TaggerCheckSTM : public IConfigurable, public Clus::IEnsembleVisitor, priv std::cout << std::get<0>(results) << " " << std::get<1>(results) << " " << std::get<2>(results) << " " << std::get<3>(results) << std::endl; std::cout << "4-momentum: " << segment_cal_4mom(segment, 22, particle_data(), m_recomb_model) << " " << segment->dirsign() << std::endl; - segment_determine_dir_track(segment, 1, 1, particle_data(), m_recomb_model, 43000/units::cm, true); - segment_determine_shower_direction_trajectory(segment, 1,1 , particle_data(), m_recomb_model, 43000/units::cm, true); - - std::set> segs; - segs.insert(segment); - clustering_points_segments(segs, m_dv, "associate_points"); + segment_determine_dir_track(segment, 1, 1, particle_data(), m_recomb_model, 43000/units::cm, true) ; + segment_determine_shower_direction_trajectory(segment, 1,1 , particle_data(), m_recomb_model, 43000/units::cm, true); + + // // hack ... + // std::set> segs; + // segs.insert(segment); + // clustering_points_segments(segs, m_dv, "associate_points"); + // std::cout << segment_is_shower_topology(segment) << " " << segment_determine_shower_direction(segment, particle_data(), m_recomb_model) << std::endl; + + + Point break_p(215.7*units::cm, -94.9437*units::cm, 211.109*units::cm); + + auto break_results = break_segment(pr_graph, segment, break_p, particle_data(), m_recomb_model, m_dv); + bool break_success = std::get<0>(break_results); + if (break_success){ + std::cout << "Break segment successful." << std::endl; + auto seg1 = std::get<1>(break_results).first; + auto seg2 = std::get<1>(break_results).second; + auto vtx_break = std::get<2>(break_results); + std::cout << " Segment 1 fits size: " << seg1->fits().size() << std::endl; + std::cout << " Segment 2 fits size: " << seg2->fits().size() << std::endl; + std::cout << " Break vertex position: " << vtx_break->fit().point << std::endl; + + std::set> segs; + segs.insert(seg1); + segs.insert(seg2); + clustering_points_segments(segs, m_dv, "associate_points"); + + auto associate_dpcloud_seg1 = seg1->dpcloud("associate_points"); // Get the DynamicPointCloud + auto associate_dpcloud_seg2 = seg2->dpcloud("associate_points"); // Get the DynamicPointCloud + + std::cout << "Fit point cloud has " << associate_dpcloud_seg1->get_points().size() << " " << associate_dpcloud_seg2->get_points().size() << " points: " << seg1->cluster()->npoints() << std::endl; + // if (associate_dpcloud) { + // const auto& points = associate_dpcloud->get_points(); + // std::cout << "Fit point cloud has " << points.size() << " points: " << segment->cluster()->npoints() << std::endl; + // } + std::cout << segment_is_shower_topology(seg1) << " " << segment_determine_shower_direction(seg1, particle_data(), m_recomb_model) << std::endl; + std::cout << segment_is_shower_topology(seg2) << " " << segment_determine_shower_direction(seg2, particle_data(), m_recomb_model) << std::endl; - auto associate_dpcloud = segment->dpcloud("associate_points"); // Get the DynamicPointCloud - if (associate_dpcloud) { - const auto& points = associate_dpcloud->get_points(); - std::cout << "Fit point cloud has " << points.size() << " points: " << segment->cluster()->npoints() << std::endl; } - std::cout << segment_is_shower_topology(segment) << " " << segment_determine_shower_direction(segment, particle_data(), m_recomb_model) << std::endl; + From 0e32d8890f9966b324377f893816e0da5adfcbfe Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Tue, 4 Nov 2025 07:18:45 -0800 Subject: [PATCH 015/111] add validation to multi-track fitting --- clus/src/TaggerCheckSTM.cxx | 87 ++++++++++++++++++++++++++++++++----- clus/src/TrackFitting.cxx | 86 +++++++++++++++++++++--------------- 2 files changed, 128 insertions(+), 45 deletions(-) diff --git a/clus/src/TaggerCheckSTM.cxx b/clus/src/TaggerCheckSTM.cxx index 6b6026d8..5d260515 100644 --- a/clus/src/TaggerCheckSTM.cxx +++ b/clus/src/TaggerCheckSTM.cxx @@ -155,10 +155,10 @@ class TaggerCheckSTM : public IConfigurable, public Clus::IEnsembleVisitor, priv // Create a PR::Graph with two vertices connected by this segment // This is needed for break_segment to work properly - WireCell::Clus::PR::Graph pr_graph; - auto vtx1 = WireCell::Clus::PR::make_vertex(pr_graph); - auto vtx2 = WireCell::Clus::PR::make_vertex(pr_graph); - WireCell::Clus::PR::add_segment(pr_graph, segment, vtx1, vtx2); + auto pr_graph = std::make_shared(); + auto vtx1 = WireCell::Clus::PR::make_vertex(*pr_graph); + auto vtx2 = WireCell::Clus::PR::make_vertex(*pr_graph); + WireCell::Clus::PR::add_segment(*pr_graph, segment, vtx1, vtx2); // geo_point_t test_p(10,10,10); // const auto& fit_seg_dpc = segment->dpcloud("main"); @@ -179,13 +179,22 @@ class TaggerCheckSTM : public IConfigurable, public Clus::IEnsembleVisitor, priv std::cout << "Fit results for " << fits.size() << " points:" << std::endl; for (size_t i = 0; i < fits.size(); ++i) { const auto& fit = fits[i]; - std::cout << " Point " << i << ": position=(" - << fit.point.x()/units::cm << ", " << fit.point.y()/units::cm << ", " << fit.point.z()/units::cm - << "), dQ=" << fit.dQ << ", dx=" << fit.dx/units::cm << std::endl; + // std::cout << " Point " << i << ": position=(" + // << fit.point.x()/units::cm << ", " << fit.point.y()/units::cm << ", " << fit.point.z()/units::cm + // << "), dQ=" << fit.dQ << ", dx=" << fit.dx/units::cm << std::endl; vec_dQ.push_back(fit.dQ); vec_dx.push_back(fit.dx); } - std::cout << std::endl; + // std::cout << std::endl; + const auto& wcpts = segment->wcpts(); + std::cout << "Segment WCPoints (" << wcpts.size() << "):" << std::endl; + for (size_t i = 0; i < wcpts.size(); ++i) { + const auto& wcp = wcpts[i]; + std::cout << " [" << i << "]: (" + << wcp.point.x()/units::cm << ", " + << wcp.point.y()/units::cm << ", " + << wcp.point.z()/units::cm << ") cm" << std::endl; + } // test point cloud fit @@ -281,6 +290,60 @@ class TaggerCheckSTM : public IConfigurable, public Clus::IEnsembleVisitor, priv segment_determine_dir_track(segment, 1, 1, particle_data(), m_recomb_model, 43000/units::cm, true) ; segment_determine_shower_direction_trajectory(segment, 1,1 , particle_data(), m_recomb_model, 43000/units::cm, true); + // Hack: Override segment wcpts with specific data + { + std::vector new_wcpts; + new_wcpts.push_back({WireCell::Point(219.539*units::cm, -86.9317*units::cm, 209.05*units::cm), 182}); + new_wcpts.push_back({WireCell::Point(219.319*units::cm, -87.3647*units::cm, 209.2*units::cm), 180}); + new_wcpts.push_back({WireCell::Point(219.099*units::cm, -87.8843*units::cm, 209.5*units::cm), 176}); + new_wcpts.push_back({WireCell::Point(218.879*units::cm, -88.2307*units::cm, 209.5*units::cm), 172}); + new_wcpts.push_back({WireCell::Point(218.659*units::cm, -88.5771*units::cm, 209.5*units::cm), 166}); + new_wcpts.push_back({WireCell::Point(218.438*units::cm, -88.9235*units::cm, 209.5*units::cm), 161}); + new_wcpts.push_back({WireCell::Point(218.218*units::cm, -89.4431*units::cm, 209.8*units::cm), 157}); + new_wcpts.push_back({WireCell::Point(218.218*units::cm, -89.7896*units::cm, 209.8*units::cm), 159}); + new_wcpts.push_back({WireCell::Point(217.998*units::cm, -90.136*units::cm, 209.8*units::cm), 155}); + new_wcpts.push_back({WireCell::Point(217.778*units::cm, -90.6556*units::cm, 210.1*units::cm), 149}); + new_wcpts.push_back({WireCell::Point(217.778*units::cm, -91.002*units::cm, 210.1*units::cm), 150}); + new_wcpts.push_back({WireCell::Point(217.337*units::cm, -91.3484*units::cm, 210.1*units::cm), 139}); + new_wcpts.push_back({WireCell::Point(217.117*units::cm, -91.868*units::cm, 210.4*units::cm), 135}); + new_wcpts.push_back({WireCell::Point(216.897*units::cm, -92.2144*units::cm, 210.4*units::cm), 130}); + new_wcpts.push_back({WireCell::Point(216.897*units::cm, -92.5608*units::cm, 210.4*units::cm), 131}); + new_wcpts.push_back({WireCell::Point(216.677*units::cm, -92.9073*units::cm, 210.4*units::cm), 125}); + new_wcpts.push_back({WireCell::Point(216.457*units::cm, -92.8206*units::cm, 210.55*units::cm), 116}); + new_wcpts.push_back({WireCell::Point(216.457*units::cm, -93.3402*units::cm, 210.85*units::cm), 118}); + new_wcpts.push_back({WireCell::Point(216.236*units::cm, -93.6867*units::cm, 210.85*units::cm), 112}); + new_wcpts.push_back({WireCell::Point(216.016*units::cm, -94.0331*units::cm, 210.85*units::cm), 105}); + new_wcpts.push_back({WireCell::Point(216.016*units::cm, -94.3795*units::cm, 210.85*units::cm), 106}); + new_wcpts.push_back({WireCell::Point(215.796*units::cm, -94.8991*units::cm, 211.15*units::cm), 96}); + new_wcpts.push_back({WireCell::Point(215.576*units::cm, -95.2455*units::cm, 211.15*units::cm), 88}); + new_wcpts.push_back({WireCell::Point(215.356*units::cm, -95.1589*units::cm, 211.3*units::cm), 87}); + new_wcpts.push_back({WireCell::Point(215.356*units::cm, -95.5053*units::cm, 211.3*units::cm), 86}); + new_wcpts.push_back({WireCell::Point(215.356*units::cm, -95.8517*units::cm, 211.3*units::cm), 85}); + new_wcpts.push_back({WireCell::Point(215.135*units::cm, -96.1982*units::cm, 211.3*units::cm), 80}); + new_wcpts.push_back({WireCell::Point(214.915*units::cm, -96.1982*units::cm, 211.3*units::cm), 76}); + new_wcpts.push_back({WireCell::Point(214.695*units::cm, -96.7178*units::cm, 211.6*units::cm), 73}); + new_wcpts.push_back({WireCell::Point(214.695*units::cm, -97.0642*units::cm, 211.6*units::cm), 74}); + new_wcpts.push_back({WireCell::Point(214.475*units::cm, -97.4106*units::cm, 211.6*units::cm), 68}); + new_wcpts.push_back({WireCell::Point(214.255*units::cm, -97.757*units::cm, 211.6*units::cm), 64}); + new_wcpts.push_back({WireCell::Point(214.034*units::cm, -98.2766*units::cm, 211.9*units::cm), 59}); + new_wcpts.push_back({WireCell::Point(213.814*units::cm, -98.623*units::cm, 211.9*units::cm), 52}); + new_wcpts.push_back({WireCell::Point(213.594*units::cm, -98.7096*units::cm, 211.75*units::cm), 47}); + new_wcpts.push_back({WireCell::Point(213.594*units::cm, -99.2292*units::cm, 212.05*units::cm), 48}); + new_wcpts.push_back({WireCell::Point(213.374*units::cm, -99.4025*units::cm, 212.05*units::cm), 40}); + new_wcpts.push_back({WireCell::Point(213.374*units::cm, -99.7489*units::cm, 212.05*units::cm), 39}); + new_wcpts.push_back({WireCell::Point(213.154*units::cm, -99.8355*units::cm, 212.2*units::cm), 35}); + new_wcpts.push_back({WireCell::Point(213.154*units::cm, -100.182*units::cm, 212.2*units::cm), 37}); + new_wcpts.push_back({WireCell::Point(212.933*units::cm, -100.442*units::cm, 212.35*units::cm), 33}); + new_wcpts.push_back({WireCell::Point(212.713*units::cm, -100.528*units::cm, 212.2*units::cm), 28}); + new_wcpts.push_back({WireCell::Point(212.493*units::cm, -101.048*units::cm, 212.5*units::cm), 23}); + new_wcpts.push_back({WireCell::Point(212.493*units::cm, -101.221*units::cm, 212.8*units::cm), 24}); + new_wcpts.push_back({WireCell::Point(212.053*units::cm, -101.481*units::cm, 212.95*units::cm), 9}); + new_wcpts.push_back({WireCell::Point(212.053*units::cm, -101.827*units::cm, 212.95*units::cm), 10}); + new_wcpts.push_back({WireCell::Point(212.053*units::cm, -103.213*units::cm, 212.95*units::cm), 15}); + segment->wcpts(new_wcpts); + std::cout << "Hacked segment wcpts with " << new_wcpts.size() << " points" << std::endl; + } + // // hack ... // std::set> segs; // segs.insert(segment); @@ -290,7 +353,7 @@ class TaggerCheckSTM : public IConfigurable, public Clus::IEnsembleVisitor, priv Point break_p(215.7*units::cm, -94.9437*units::cm, 211.109*units::cm); - auto break_results = break_segment(pr_graph, segment, break_p, particle_data(), m_recomb_model, m_dv); + auto break_results = break_segment(*pr_graph, segment, break_p, particle_data(), m_recomb_model, m_dv); bool break_success = std::get<0>(break_results); if (break_success){ std::cout << "Break segment successful." << std::endl; @@ -319,7 +382,11 @@ class TaggerCheckSTM : public IConfigurable, public Clus::IEnsembleVisitor, priv } - + + + m_track_fitter.add_graph(pr_graph); + m_track_fitter.do_multi_tracking(true, true, false); + diff --git a/clus/src/TrackFitting.cxx b/clus/src/TrackFitting.cxx index aba82dd8..e8a73d60 100644 --- a/clus/src/TrackFitting.cxx +++ b/clus/src/TrackFitting.cxx @@ -6472,57 +6472,73 @@ void TrackFitting::do_multi_tracking(bool flag_dQ_dx_fit_reg, bool flag_dQ_dx_fi fill_global_rb_map(); } + + auto edge_range = boost::edges(*m_graph); + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + auto& edge_bundle = (*m_graph)[*e_it]; + if (edge_bundle.segment) { + std::cout << "Segment fits size: " << edge_bundle.segment->fits().size() << std::endl; + } + } + // First round of organizing the path from the path_wcps (shortest path) double low_dis_limit = m_params.low_dis_limit; double end_point_limit = m_params.end_point_limit; organize_segments_path(low_dis_limit, end_point_limit); + std::cout << "After first organization " << std::endl; - - if (flag_1st_tracking){ - form_map_graph(flag_exclusion, m_params.end_point_factor, m_params.mid_point_factor, m_params.nlevel, m_params.time_tick_cut, m_params.charge_cut); - multi_trajectory_fit(1, m_params.div_sigma); + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + auto& edge_bundle = (*m_graph)[*e_it]; + if (edge_bundle.segment) { + std::cout << "Segment fits size: " << edge_bundle.segment->fits().size() << std::endl; + } } + + // if (flag_1st_tracking){ + // form_map_graph(flag_exclusion, m_params.end_point_factor, m_params.mid_point_factor, m_params.nlevel, m_params.time_tick_cut, m_params.charge_cut); + // multi_trajectory_fit(1, m_params.div_sigma); + // } - if (flag_2nd_tracking){ - // second round trajectory fit ... - low_dis_limit = m_params.low_dis_limit/2.; - end_point_limit = m_params.end_point_limit/2.; + // if (flag_2nd_tracking){ + // // second round trajectory fit ... + // low_dis_limit = m_params.low_dis_limit/2.; + // end_point_limit = m_params.end_point_limit/2.; - // organize path - organize_segments_path_2nd(low_dis_limit, end_point_limit); + // // organize path + // organize_segments_path_2nd(low_dis_limit, end_point_limit); - form_map_graph(flag_exclusion, m_params.end_point_factor, m_params.mid_point_factor, m_params.nlevel, m_params.time_tick_cut, m_params.charge_cut); + // form_map_graph(flag_exclusion, m_params.end_point_factor, m_params.mid_point_factor, m_params.nlevel, m_params.time_tick_cut, m_params.charge_cut); - multi_trajectory_fit(1, m_params.div_sigma); + // multi_trajectory_fit(1, m_params.div_sigma); - // organize path - low_dis_limit = 0.6*units::cm; - organize_segments_path_3rd(low_dis_limit); - } + // // organize path + // low_dis_limit = 0.6*units::cm; + // organize_segments_path_3rd(low_dis_limit); + // } - if (flag_dQ_dx){ - for (auto vp = boost::vertices(*m_graph); vp.first != vp.second; ++vp.first) { - auto vd = *vp.first; - auto& v_bundle = (*m_graph)[vd]; - if (v_bundle.vertex) { - bool flag_fix = v_bundle.vertex->flag_fix(); - v_bundle.vertex->reset_fit_prop(); - v_bundle.vertex->flag_fix(flag_fix); - } - } + // if (flag_dQ_dx){ + // for (auto vp = boost::vertices(*m_graph); vp.first != vp.second; ++vp.first) { + // auto vd = *vp.first; + // auto& v_bundle = (*m_graph)[vd]; + // if (v_bundle.vertex) { + // bool flag_fix = v_bundle.vertex->flag_fix(); + // v_bundle.vertex->reset_fit_prop(); + // v_bundle.vertex->flag_fix(flag_fix); + // } + // } - auto edge_range = boost::edges(*m_graph); - for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { - auto& edge_bundle = (*m_graph)[*e_it]; - if (edge_bundle.segment) { - edge_bundle.segment->reset_fit_prop(); - } - } - dQ_dx_multi_fit(end_point_limit, flag_dQ_dx_fit_reg); - } + // auto edge_range = boost::edges(*m_graph); + // for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + // auto& edge_bundle = (*m_graph)[*e_it]; + // if (edge_bundle.segment) { + // edge_bundle.segment->reset_fit_prop(); + // } + // } + // dQ_dx_multi_fit(end_point_limit, flag_dQ_dx_fit_reg); + // } } From a75fd2063e031059e82044a77ddf461248989c8d Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Tue, 4 Nov 2025 12:47:40 -0800 Subject: [PATCH 016/111] fix a bug --- clus/src/PRSegmentFunctions.cxx | 2 +- clus/src/TaggerCheckSTM.cxx | 5 ++-- clus/src/TrackFitting.cxx | 53 ++++++++++++++++++++++++++------- 3 files changed, 46 insertions(+), 14 deletions(-) diff --git a/clus/src/PRSegmentFunctions.cxx b/clus/src/PRSegmentFunctions.cxx index e528df7d..d130ed67 100644 --- a/clus/src/PRSegmentFunctions.cxx +++ b/clus/src/PRSegmentFunctions.cxx @@ -439,7 +439,7 @@ namespace WireCell::Clus::PR { // WARNING there is no "direction" in the graph. You can not assume the // "source()" of a segment is closest to the segments first point. As // of now, at least... - auto seg1 = make_segment(graph, vtx, vtx1); + auto seg1 = make_segment(graph, vtx1, vtx); auto seg2 = make_segment(graph, vtx, vtx2); diff --git a/clus/src/TaggerCheckSTM.cxx b/clus/src/TaggerCheckSTM.cxx index 5d260515..b9e5c3b8 100644 --- a/clus/src/TaggerCheckSTM.cxx +++ b/clus/src/TaggerCheckSTM.cxx @@ -158,6 +158,8 @@ class TaggerCheckSTM : public IConfigurable, public Clus::IEnsembleVisitor, priv auto pr_graph = std::make_shared(); auto vtx1 = WireCell::Clus::PR::make_vertex(*pr_graph); auto vtx2 = WireCell::Clus::PR::make_vertex(*pr_graph); + vtx1->wcpt().point = first_wcp; + vtx2->wcpt().point = last_wcp; WireCell::Clus::PR::add_segment(*pr_graph, segment, vtx1, vtx2); // geo_point_t test_p(10,10,10); @@ -362,7 +364,7 @@ class TaggerCheckSTM : public IConfigurable, public Clus::IEnsembleVisitor, priv auto vtx_break = std::get<2>(break_results); std::cout << " Segment 1 fits size: " << seg1->fits().size() << std::endl; std::cout << " Segment 2 fits size: " << seg2->fits().size() << std::endl; - std::cout << " Break vertex position: " << vtx_break->fit().point << std::endl; + std::cout << " Break vertex position: " << vtx_break->fit().point << " " << vtx_break->wcpt().point << std::endl; std::set> segs; segs.insert(seg1); @@ -380,7 +382,6 @@ class TaggerCheckSTM : public IConfigurable, public Clus::IEnsembleVisitor, priv std::cout << segment_is_shower_topology(seg1) << " " << segment_determine_shower_direction(seg1, particle_data(), m_recomb_model) << std::endl; std::cout << segment_is_shower_topology(seg2) << " " << segment_determine_shower_direction(seg2, particle_data(), m_recomb_model) << std::endl; - } diff --git a/clus/src/TrackFitting.cxx b/clus/src/TrackFitting.cxx index e8a73d60..b5773b40 100644 --- a/clus/src/TrackFitting.cxx +++ b/clus/src/TrackFitting.cxx @@ -2843,6 +2843,9 @@ void TrackFitting::form_map_graph(bool flag_exclusion, double end_point_factor, } } + // std::cout << "Form Map: " << " " << saved_pts.size() << " " << m_2d_to_3d.size() << " " << m_3d_to_2d.size() << std::endl; + + // Set fit associate vector for the segment segment->set_fit_associate_vec(saved_pts, saved_index, saved_skip, m_dv, "fit"); } @@ -2890,6 +2893,8 @@ void TrackFitting::form_map_graph(bool flag_exclusion, double end_point_factor, } } } + + // std::cout << "Form Map: " << " I " << " " << m_2d_to_3d.size() << " " << m_3d_to_2d.size() << std::endl; } @@ -3425,6 +3430,8 @@ void TrackFitting::multi_trajectory_fit(int charge_div_method, double div_sigma) offset_t, slope_x, offset_u, slope_yu, slope_zu, offset_v, slope_yv, slope_zv, offset_w, slope_yw, slope_zw); + // std::cout << "Vertex Fit " << i << " " << init_p << " --> " << fitted_p << std::endl; + // Update vertex with fitted position and projections auto& vertex_fit = vertex->fit(); vertex_fit.point = fitted_p; @@ -3530,6 +3537,7 @@ void TrackFitting::multi_trajectory_fit(int charge_div_method, double div_sigma) } } } + // std::cout << "Fit point Track " << i << ": (" << init_ps[i].x() << ", " << init_ps[i].y() << ", " << init_ps[i].z() << ")" << " : (" << temp_p.x() << ", " << temp_p.y() << ", " << temp_p.z() << ")" << std::endl; final_ps.push_back(temp_p); } } @@ -3537,6 +3545,8 @@ void TrackFitting::multi_trajectory_fit(int charge_div_method, double div_sigma) // Apply trajectory examination/smoothing std::vector examined_ps = examine_segment_trajectory(segment, final_ps, init_ps); + std::cout << " fitted with " << examined_ps.size() << " " << init_ps.size() << " " << final_ps.size() << " points." << std::endl; + // Update segment with fitted results std::vector new_fits; for (size_t i = 0; i < examined_ps.size(); i++) { @@ -4176,6 +4186,8 @@ std::vector TrackFitting::examine_segment_trajectory(std::share } for (size_t i = 0; i < final_ps_vec.size(); i++) { + // std::cout << i << " " << final_ps_vec[i].x() << " " << final_ps_vec[i].y() << " " << final_ps_vec[i].z() << " : " << init_ps_vec[i].x() << " " << init_ps_vec[i].y() << " " << init_ps_vec[i].z() << std::endl; + pss_vec.push_back(std::make_pair(final_ps_vec[i], segment)); } @@ -6446,6 +6458,7 @@ void WireCell::Clus::TrackFitting::dQ_dx_fit(double dis_end_point_ext, bool flag } void TrackFitting::do_multi_tracking(bool flag_dQ_dx_fit_reg, bool flag_dQ_dx_fit, bool flag_force_load_data, bool flag_exclusion, bool flag_hack){ + // Reset fit properties for all vertices first for (auto vp = boost::vertices(*m_graph); vp.first != vp.second; ++vp.first) { auto vd = *vp.first; @@ -6457,6 +6470,8 @@ void TrackFitting::do_multi_tracking(bool flag_dQ_dx_fit_reg, bool flag_dQ_dx_fi auto& vertex_fit = v_bundle.vertex->fit(); vertex_fit.point = p; } + + // std::cout << "Vertex fit point before reset: " << flag_fix << " " << v_bundle.vertex->fit().point << " " << v_bundle.vertex->wcpt().point << std::endl; // v_bundle.vertex->reset_fit_prop(); // v_bundle.vertex->flag_fix(flag_fix); } @@ -6495,20 +6510,36 @@ void TrackFitting::do_multi_tracking(bool flag_dQ_dx_fit_reg, bool flag_dQ_dx_fi } } - // if (flag_1st_tracking){ - // form_map_graph(flag_exclusion, m_params.end_point_factor, m_params.mid_point_factor, m_params.nlevel, m_params.time_tick_cut, m_params.charge_cut); - // multi_trajectory_fit(1, m_params.div_sigma); - // } + if (flag_1st_tracking){ + form_map_graph(flag_exclusion, m_params.end_point_factor, m_params.mid_point_factor, m_params.nlevel, m_params.time_tick_cut, m_params.charge_cut); + multi_trajectory_fit(1, m_params.div_sigma); + } - // if (flag_2nd_tracking){ - // // second round trajectory fit ... - // low_dis_limit = m_params.low_dis_limit/2.; - // end_point_limit = m_params.end_point_limit/2.; + if (flag_2nd_tracking){ + // second round trajectory fit ... + low_dis_limit = m_params.low_dis_limit/2.; + end_point_limit = m_params.end_point_limit/2.; - // // organize path - // organize_segments_path_2nd(low_dis_limit, end_point_limit); + auto edge_range = boost::edges(*m_graph); + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + auto& edge_bundle = (*m_graph)[*e_it]; + if (edge_bundle.segment) { + std::cout << "Segment fits size: " << edge_bundle.segment->fits().size() << std::endl; + } + } + + // organize path + organize_segments_path_2nd(low_dis_limit, end_point_limit); + std::cout << "After second organization " << std::endl; + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + auto& edge_bundle = (*m_graph)[*e_it]; + if (edge_bundle.segment) { + std::cout << "Segment fits size: " << edge_bundle.segment->fits().size() << std::endl; + } + } + // form_map_graph(flag_exclusion, m_params.end_point_factor, m_params.mid_point_factor, m_params.nlevel, m_params.time_tick_cut, m_params.charge_cut); // multi_trajectory_fit(1, m_params.div_sigma); @@ -6516,7 +6547,7 @@ void TrackFitting::do_multi_tracking(bool flag_dQ_dx_fit_reg, bool flag_dQ_dx_fi // // organize path // low_dis_limit = 0.6*units::cm; // organize_segments_path_3rd(low_dis_limit); - // } + } // if (flag_dQ_dx){ From ad02e81cfad774e3d42e937cb7b24c95c808fe8c Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Wed, 5 Nov 2025 05:57:19 -0800 Subject: [PATCH 017/111] catch up --- clus/src/TrackFitting.cxx | 76 +++++++++++++++++++++------------------ 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/clus/src/TrackFitting.cxx b/clus/src/TrackFitting.cxx index b5773b40..645449ba 100644 --- a/clus/src/TrackFitting.cxx +++ b/clus/src/TrackFitting.cxx @@ -3545,7 +3545,7 @@ void TrackFitting::multi_trajectory_fit(int charge_div_method, double div_sigma) // Apply trajectory examination/smoothing std::vector examined_ps = examine_segment_trajectory(segment, final_ps, init_ps); - std::cout << " fitted with " << examined_ps.size() << " " << init_ps.size() << " " << final_ps.size() << " points." << std::endl; + // std::cout << " fitted with " << examined_ps.size() << " " << init_ps.size() << " " << final_ps.size() << " points." << std::endl; // Update segment with fitted results std::vector new_fits; @@ -6488,27 +6488,27 @@ void TrackFitting::do_multi_tracking(bool flag_dQ_dx_fit_reg, bool flag_dQ_dx_fi } - auto edge_range = boost::edges(*m_graph); - for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { - auto& edge_bundle = (*m_graph)[*e_it]; - if (edge_bundle.segment) { - std::cout << "Segment fits size: " << edge_bundle.segment->fits().size() << std::endl; - } - } + // auto edge_range = boost::edges(*m_graph); + // for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + // auto& edge_bundle = (*m_graph)[*e_it]; + // if (edge_bundle.segment) { + // std::cout << "Segment fits size: " << edge_bundle.segment->fits().size() << std::endl; + // } + // } // First round of organizing the path from the path_wcps (shortest path) double low_dis_limit = m_params.low_dis_limit; double end_point_limit = m_params.end_point_limit; organize_segments_path(low_dis_limit, end_point_limit); - std::cout << "After first organization " << std::endl; + // std::cout << "After first organization " << std::endl; - for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { - auto& edge_bundle = (*m_graph)[*e_it]; - if (edge_bundle.segment) { - std::cout << "Segment fits size: " << edge_bundle.segment->fits().size() << std::endl; - } - } + // for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + // auto& edge_bundle = (*m_graph)[*e_it]; + // if (edge_bundle.segment) { + // std::cout << "Segment fits size: " << edge_bundle.segment->fits().size() << std::endl; + // } + // } if (flag_1st_tracking){ form_map_graph(flag_exclusion, m_params.end_point_factor, m_params.mid_point_factor, m_params.nlevel, m_params.time_tick_cut, m_params.charge_cut); @@ -6521,32 +6521,40 @@ void TrackFitting::do_multi_tracking(bool flag_dQ_dx_fit_reg, bool flag_dQ_dx_fi low_dis_limit = m_params.low_dis_limit/2.; end_point_limit = m_params.end_point_limit/2.; - auto edge_range = boost::edges(*m_graph); - for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { - auto& edge_bundle = (*m_graph)[*e_it]; - if (edge_bundle.segment) { - std::cout << "Segment fits size: " << edge_bundle.segment->fits().size() << std::endl; - } - } + // auto edge_range = boost::edges(*m_graph); + // for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + // auto& edge_bundle = (*m_graph)[*e_it]; + // if (edge_bundle.segment) { + // std::cout << "Segment fits size: " << edge_bundle.segment->fits().size() << std::endl; + // } + // } // organize path organize_segments_path_2nd(low_dis_limit, end_point_limit); - std::cout << "After second organization " << std::endl; - for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { - auto& edge_bundle = (*m_graph)[*e_it]; - if (edge_bundle.segment) { - std::cout << "Segment fits size: " << edge_bundle.segment->fits().size() << std::endl; - } - } + // std::cout << "After second organization " << std::endl; + // for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + // auto& edge_bundle = (*m_graph)[*e_it]; + // if (edge_bundle.segment) { + // std::cout << "Segment fits size: " << edge_bundle.segment->fits().size() << std::endl; + // } + // } - // form_map_graph(flag_exclusion, m_params.end_point_factor, m_params.mid_point_factor, m_params.nlevel, m_params.time_tick_cut, m_params.charge_cut); + form_map_graph(flag_exclusion, m_params.end_point_factor, m_params.mid_point_factor, m_params.nlevel, m_params.time_tick_cut, m_params.charge_cut); - // multi_trajectory_fit(1, m_params.div_sigma); + multi_trajectory_fit(1, m_params.div_sigma); - // // organize path - // low_dis_limit = 0.6*units::cm; - // organize_segments_path_3rd(low_dis_limit); + // organize path + low_dis_limit = 0.6*units::cm; + organize_segments_path_3rd(low_dis_limit); + + // std::cout << "After third organization " << std::endl; + // for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + // auto& edge_bundle = (*m_graph)[*e_it]; + // if (edge_bundle.segment) { + // std::cout << "Segment fits size: " << edge_bundle.segment->fits().size() << std::endl; + // } + // } } From fca0f7cfdc0a94022cb7eca06134d1f25b83fa9a Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Wed, 5 Nov 2025 17:46:50 -0800 Subject: [PATCH 018/111] continue dbug --- clus/inc/WireCellClus/PRCommon.h | 12 +- clus/inc/WireCellClus/PRSegment.h | 2 +- clus/inc/WireCellClus/TrackFitting.h | 2 +- clus/src/PRSegment.cxx | 35 +- clus/src/TaggerCheckSTM.cxx | 94 +-- clus/src/TrackFitting.cxx | 1063 +++++++++++++++++++++----- 6 files changed, 942 insertions(+), 266 deletions(-) diff --git a/clus/inc/WireCellClus/PRCommon.h b/clus/inc/WireCellClus/PRCommon.h index 48769bbe..abbc3679 100644 --- a/clus/inc/WireCellClus/PRCommon.h +++ b/clus/inc/WireCellClus/PRCommon.h @@ -95,18 +95,18 @@ namespace WireCell::Clus::PR { // multi APA/face detectors? struct WCPoint { WireCell::Point point; // 3D point - int uvw[3] = {-1,-1,-1}; // wire indices - int index{-1}; // point index in some container + // int uvw[3] = {-1,-1,-1}; // wire indices + // int index{-1}; // point index in some container // FIXME: WCP had this, does WCT need it? // blob* b; // Return true if the point information has been filled. - bool valid() const { - if (index < 0) return false; - return true; - } + // bool valid() const { + // if (index < 0) return false; + // return true; + // } }; using WCPointVector = std::vector; diff --git a/clus/inc/WireCellClus/PRSegment.h b/clus/inc/WireCellClus/PRSegment.h index 7dc0a310..c4c8d58d 100644 --- a/clus/inc/WireCellClus/PRSegment.h +++ b/clus/inc/WireCellClus/PRSegment.h @@ -115,7 +115,7 @@ namespace WireCell::Clus::PR { bool fit_flag_skip(int i); void fit_flag_skip(int i, bool flag); - void set_fit_associate_vec(std::vector& tmp_fit_pt_vec, std::vector& tmp_fit_index, std::vector& tmp_fit_skip, const IDetectorVolumes::pointer& dv,const std::string& cloud_name="fit"); + void set_fit_associate_vec(std::vector& tmp_fit_vec, const IDetectorVolumes::pointer& dv,const std::string& cloud_name="fit"); private: diff --git a/clus/inc/WireCellClus/TrackFitting.h b/clus/inc/WireCellClus/TrackFitting.h index 8c1b6a93..c25ea6c3 100644 --- a/clus/inc/WireCellClus/TrackFitting.h +++ b/clus/inc/WireCellClus/TrackFitting.h @@ -331,7 +331,7 @@ namespace WireCell::Clus { * Each pair contains (previous_neighbor_ratio, next_neighbor_ratio) */ std::vector> calculate_compact_matrix(Eigen::SparseMatrix& weight_matrix, const Eigen::SparseMatrix& response_matrix_transpose, int n_2d_measurements, int n_3d_positions, double cut_position = 2.0); - std::vector> calculate_compact_matrix_multi(std::vector >& connected_vec,Eigen::SparseMatrix& weight_matrix, const Eigen::SparseMatrix& response_matrix_transpose, int n_2d_measurements, int n_3d_positions, double cut_position = 2.0); + std::vector> calculate_compact_matrix_multi(std::vector >& connected_vec,Eigen::SparseMatrix& weight_matrix, const Eigen::SparseMatrix& response_matrix_transpose, int n_2d_measurements, int n_3d_positions, double cut_position = 2.0); void dQ_dx_fill(double dis_end_point_ext=0.45*units::cm); diff --git a/clus/src/PRSegment.cxx b/clus/src/PRSegment.cxx index ffc6354b..d458eec9 100644 --- a/clus/src/PRSegment.cxx +++ b/clus/src/PRSegment.cxx @@ -34,25 +34,24 @@ namespace WireCell::Clus::PR { m_fits[i].flag_fix = flag; } - void Segment::set_fit_associate_vec(std::vector& tmp_fit_pt_vec, std::vector& tmp_fit_index, std::vector& tmp_fit_skip, const IDetectorVolumes::pointer& dv,const std::string& cloud_name){ + void Segment::set_fit_associate_vec(std::vector& tmp_fit_vec, const IDetectorVolumes::pointer& dv,const std::string& cloud_name){ // Store fit points in m_fits vector - m_fits.clear(); - m_fits.reserve(tmp_fit_pt_vec.size()); - - for (size_t i = 0; i < tmp_fit_pt_vec.size(); ++i) { - Fit fit; - // Convert WCP::Point to WireCell::Point - fit.point = WireCell::Point(tmp_fit_pt_vec[i].x(), tmp_fit_pt_vec[i].y(), tmp_fit_pt_vec[i].z()); - if (i < tmp_fit_index.size()) { - fit.index = tmp_fit_index[i]; - } - if (i < tmp_fit_skip.size()) { - if (tmp_fit_skip[i]) { - fit.flag_fix = true; - } - } - m_fits.push_back(fit); - } + m_fits = tmp_fit_vec; + + // for (size_t i = 0; i < tmp_fit_pt_vec.size(); ++i) { + // Fit fit; + // // Convert WCP::Point to WireCell::Point + // fit.point = WireCell::Point(tmp_fit_pt_vec[i].x(), tmp_fit_pt_vec[i].y(), tmp_fit_pt_vec[i].z()); + // if (i < tmp_fit_index.size()) { + // fit.index = tmp_fit_index[i]; + // } + // if (i < tmp_fit_skip.size()) { + // if (tmp_fit_skip[i]) { + // fit.flag_fix = true; + // } + // } + // m_fits.push_back(fit); + // } // Create dynamic point cloud with the fit points if (dv) { diff --git a/clus/src/TaggerCheckSTM.cxx b/clus/src/TaggerCheckSTM.cxx index b9e5c3b8..76df6956 100644 --- a/clus/src/TaggerCheckSTM.cxx +++ b/clus/src/TaggerCheckSTM.cxx @@ -295,53 +295,53 @@ class TaggerCheckSTM : public IConfigurable, public Clus::IEnsembleVisitor, priv // Hack: Override segment wcpts with specific data { std::vector new_wcpts; - new_wcpts.push_back({WireCell::Point(219.539*units::cm, -86.9317*units::cm, 209.05*units::cm), 182}); - new_wcpts.push_back({WireCell::Point(219.319*units::cm, -87.3647*units::cm, 209.2*units::cm), 180}); - new_wcpts.push_back({WireCell::Point(219.099*units::cm, -87.8843*units::cm, 209.5*units::cm), 176}); - new_wcpts.push_back({WireCell::Point(218.879*units::cm, -88.2307*units::cm, 209.5*units::cm), 172}); - new_wcpts.push_back({WireCell::Point(218.659*units::cm, -88.5771*units::cm, 209.5*units::cm), 166}); - new_wcpts.push_back({WireCell::Point(218.438*units::cm, -88.9235*units::cm, 209.5*units::cm), 161}); - new_wcpts.push_back({WireCell::Point(218.218*units::cm, -89.4431*units::cm, 209.8*units::cm), 157}); - new_wcpts.push_back({WireCell::Point(218.218*units::cm, -89.7896*units::cm, 209.8*units::cm), 159}); - new_wcpts.push_back({WireCell::Point(217.998*units::cm, -90.136*units::cm, 209.8*units::cm), 155}); - new_wcpts.push_back({WireCell::Point(217.778*units::cm, -90.6556*units::cm, 210.1*units::cm), 149}); - new_wcpts.push_back({WireCell::Point(217.778*units::cm, -91.002*units::cm, 210.1*units::cm), 150}); - new_wcpts.push_back({WireCell::Point(217.337*units::cm, -91.3484*units::cm, 210.1*units::cm), 139}); - new_wcpts.push_back({WireCell::Point(217.117*units::cm, -91.868*units::cm, 210.4*units::cm), 135}); - new_wcpts.push_back({WireCell::Point(216.897*units::cm, -92.2144*units::cm, 210.4*units::cm), 130}); - new_wcpts.push_back({WireCell::Point(216.897*units::cm, -92.5608*units::cm, 210.4*units::cm), 131}); - new_wcpts.push_back({WireCell::Point(216.677*units::cm, -92.9073*units::cm, 210.4*units::cm), 125}); - new_wcpts.push_back({WireCell::Point(216.457*units::cm, -92.8206*units::cm, 210.55*units::cm), 116}); - new_wcpts.push_back({WireCell::Point(216.457*units::cm, -93.3402*units::cm, 210.85*units::cm), 118}); - new_wcpts.push_back({WireCell::Point(216.236*units::cm, -93.6867*units::cm, 210.85*units::cm), 112}); - new_wcpts.push_back({WireCell::Point(216.016*units::cm, -94.0331*units::cm, 210.85*units::cm), 105}); - new_wcpts.push_back({WireCell::Point(216.016*units::cm, -94.3795*units::cm, 210.85*units::cm), 106}); - new_wcpts.push_back({WireCell::Point(215.796*units::cm, -94.8991*units::cm, 211.15*units::cm), 96}); - new_wcpts.push_back({WireCell::Point(215.576*units::cm, -95.2455*units::cm, 211.15*units::cm), 88}); - new_wcpts.push_back({WireCell::Point(215.356*units::cm, -95.1589*units::cm, 211.3*units::cm), 87}); - new_wcpts.push_back({WireCell::Point(215.356*units::cm, -95.5053*units::cm, 211.3*units::cm), 86}); - new_wcpts.push_back({WireCell::Point(215.356*units::cm, -95.8517*units::cm, 211.3*units::cm), 85}); - new_wcpts.push_back({WireCell::Point(215.135*units::cm, -96.1982*units::cm, 211.3*units::cm), 80}); - new_wcpts.push_back({WireCell::Point(214.915*units::cm, -96.1982*units::cm, 211.3*units::cm), 76}); - new_wcpts.push_back({WireCell::Point(214.695*units::cm, -96.7178*units::cm, 211.6*units::cm), 73}); - new_wcpts.push_back({WireCell::Point(214.695*units::cm, -97.0642*units::cm, 211.6*units::cm), 74}); - new_wcpts.push_back({WireCell::Point(214.475*units::cm, -97.4106*units::cm, 211.6*units::cm), 68}); - new_wcpts.push_back({WireCell::Point(214.255*units::cm, -97.757*units::cm, 211.6*units::cm), 64}); - new_wcpts.push_back({WireCell::Point(214.034*units::cm, -98.2766*units::cm, 211.9*units::cm), 59}); - new_wcpts.push_back({WireCell::Point(213.814*units::cm, -98.623*units::cm, 211.9*units::cm), 52}); - new_wcpts.push_back({WireCell::Point(213.594*units::cm, -98.7096*units::cm, 211.75*units::cm), 47}); - new_wcpts.push_back({WireCell::Point(213.594*units::cm, -99.2292*units::cm, 212.05*units::cm), 48}); - new_wcpts.push_back({WireCell::Point(213.374*units::cm, -99.4025*units::cm, 212.05*units::cm), 40}); - new_wcpts.push_back({WireCell::Point(213.374*units::cm, -99.7489*units::cm, 212.05*units::cm), 39}); - new_wcpts.push_back({WireCell::Point(213.154*units::cm, -99.8355*units::cm, 212.2*units::cm), 35}); - new_wcpts.push_back({WireCell::Point(213.154*units::cm, -100.182*units::cm, 212.2*units::cm), 37}); - new_wcpts.push_back({WireCell::Point(212.933*units::cm, -100.442*units::cm, 212.35*units::cm), 33}); - new_wcpts.push_back({WireCell::Point(212.713*units::cm, -100.528*units::cm, 212.2*units::cm), 28}); - new_wcpts.push_back({WireCell::Point(212.493*units::cm, -101.048*units::cm, 212.5*units::cm), 23}); - new_wcpts.push_back({WireCell::Point(212.493*units::cm, -101.221*units::cm, 212.8*units::cm), 24}); - new_wcpts.push_back({WireCell::Point(212.053*units::cm, -101.481*units::cm, 212.95*units::cm), 9}); - new_wcpts.push_back({WireCell::Point(212.053*units::cm, -101.827*units::cm, 212.95*units::cm), 10}); - new_wcpts.push_back({WireCell::Point(212.053*units::cm, -103.213*units::cm, 212.95*units::cm), 15}); + new_wcpts.push_back({WireCell::Point(219.539*units::cm, -86.9317*units::cm, 209.05*units::cm)}); + new_wcpts.push_back({WireCell::Point(219.319*units::cm, -87.3647*units::cm, 209.2*units::cm)}); + new_wcpts.push_back({WireCell::Point(219.099*units::cm, -87.8843*units::cm, 209.5*units::cm)}); + new_wcpts.push_back({WireCell::Point(218.879*units::cm, -88.2307*units::cm, 209.5*units::cm)}); + new_wcpts.push_back({WireCell::Point(218.659*units::cm, -88.5771*units::cm, 209.5*units::cm)}); + new_wcpts.push_back({WireCell::Point(218.438*units::cm, -88.9235*units::cm, 209.5*units::cm)}); + new_wcpts.push_back({WireCell::Point(218.218*units::cm, -89.4431*units::cm, 209.8*units::cm)}); + new_wcpts.push_back({WireCell::Point(218.218*units::cm, -89.7896*units::cm, 209.8*units::cm)}); + new_wcpts.push_back({WireCell::Point(217.998*units::cm, -90.136*units::cm, 209.8*units::cm)}); + new_wcpts.push_back({WireCell::Point(217.778*units::cm, -90.6556*units::cm, 210.1*units::cm)}); + new_wcpts.push_back({WireCell::Point(217.778*units::cm, -91.002*units::cm, 210.1*units::cm)}); + new_wcpts.push_back({WireCell::Point(217.337*units::cm, -91.3484*units::cm, 210.1*units::cm)}); + new_wcpts.push_back({WireCell::Point(217.117*units::cm, -91.868*units::cm, 210.4*units::cm)}); + new_wcpts.push_back({WireCell::Point(216.897*units::cm, -92.2144*units::cm, 210.4*units::cm)}); + new_wcpts.push_back({WireCell::Point(216.897*units::cm, -92.5608*units::cm, 210.4*units::cm)}); + new_wcpts.push_back({WireCell::Point(216.677*units::cm, -92.9073*units::cm, 210.4*units::cm)}); + new_wcpts.push_back({WireCell::Point(216.457*units::cm, -92.8206*units::cm, 210.55*units::cm)}); + new_wcpts.push_back({WireCell::Point(216.457*units::cm, -93.3402*units::cm, 210.85*units::cm)}); + new_wcpts.push_back({WireCell::Point(216.236*units::cm, -93.6867*units::cm, 210.85*units::cm)}); + new_wcpts.push_back({WireCell::Point(216.016*units::cm, -94.0331*units::cm, 210.85*units::cm)}); + new_wcpts.push_back({WireCell::Point(216.016*units::cm, -94.3795*units::cm, 210.85*units::cm)}); + new_wcpts.push_back({WireCell::Point(215.796*units::cm, -94.8991*units::cm, 211.15*units::cm)}); + new_wcpts.push_back({WireCell::Point(215.576*units::cm, -95.2455*units::cm, 211.15*units::cm)}); + new_wcpts.push_back({WireCell::Point(215.356*units::cm, -95.1589*units::cm, 211.3*units::cm)}); + new_wcpts.push_back({WireCell::Point(215.356*units::cm, -95.5053*units::cm, 211.3*units::cm)}); + new_wcpts.push_back({WireCell::Point(215.356*units::cm, -95.8517*units::cm, 211.3*units::cm)}); + new_wcpts.push_back({WireCell::Point(215.135*units::cm, -96.1982*units::cm, 211.3*units::cm)}); + new_wcpts.push_back({WireCell::Point(214.915*units::cm, -96.1982*units::cm, 211.3*units::cm)}); + new_wcpts.push_back({WireCell::Point(214.695*units::cm, -96.7178*units::cm, 211.6*units::cm)}); + new_wcpts.push_back({WireCell::Point(214.695*units::cm, -97.0642*units::cm, 211.6*units::cm)}); + new_wcpts.push_back({WireCell::Point(214.475*units::cm, -97.4106*units::cm, 211.6*units::cm)}); + new_wcpts.push_back({WireCell::Point(214.255*units::cm, -97.757*units::cm, 211.6*units::cm)}); + new_wcpts.push_back({WireCell::Point(214.034*units::cm, -98.2766*units::cm, 211.9*units::cm)}); + new_wcpts.push_back({WireCell::Point(213.814*units::cm, -98.623*units::cm, 211.9*units::cm)}); + new_wcpts.push_back({WireCell::Point(213.594*units::cm, -98.7096*units::cm, 211.75*units::cm)}); + new_wcpts.push_back({WireCell::Point(213.594*units::cm, -99.2292*units::cm, 212.05*units::cm)}); + new_wcpts.push_back({WireCell::Point(213.374*units::cm, -99.4025*units::cm, 212.05*units::cm)}); + new_wcpts.push_back({WireCell::Point(213.374*units::cm, -99.7489*units::cm, 212.05*units::cm)}); + new_wcpts.push_back({WireCell::Point(213.154*units::cm, -99.8355*units::cm, 212.2*units::cm)}); + new_wcpts.push_back({WireCell::Point(213.154*units::cm, -100.182*units::cm, 212.2*units::cm)}); + new_wcpts.push_back({WireCell::Point(212.933*units::cm, -100.442*units::cm, 212.35*units::cm)}); + new_wcpts.push_back({WireCell::Point(212.713*units::cm, -100.528*units::cm, 212.2*units::cm)}); + new_wcpts.push_back({WireCell::Point(212.493*units::cm, -101.048*units::cm, 212.5*units::cm)}); + new_wcpts.push_back({WireCell::Point(212.493*units::cm, -101.221*units::cm, 212.8*units::cm)}); + new_wcpts.push_back({WireCell::Point(212.053*units::cm, -101.481*units::cm, 212.95*units::cm)}); + new_wcpts.push_back({WireCell::Point(212.053*units::cm, -101.827*units::cm, 212.95*units::cm)}); + new_wcpts.push_back({WireCell::Point(212.053*units::cm, -103.213*units::cm, 212.95*units::cm)}); segment->wcpts(new_wcpts); std::cout << "Hacked segment wcpts with " << new_wcpts.size() << " points" << std::endl; } diff --git a/clus/src/TrackFitting.cxx b/clus/src/TrackFitting.cxx index 645449ba..bbf9aa57 100644 --- a/clus/src/TrackFitting.cxx +++ b/clus/src/TrackFitting.cxx @@ -831,7 +831,14 @@ void TrackFitting::check_and_reset_close_vertices() { if (segment_wcpts.empty()) continue; // Check vertex ordering by comparing with segment endpoints - if (start_v->wcpt().index != segment_wcpts.front().index) { + // Use wcpt().point to determine which vertex matches the segment's front/back + double dist_start_front = (start_v->wcpt().point - segment_wcpts.front().point).magnitude(); + double dist_start_back = (start_v->wcpt().point - segment_wcpts.back().point).magnitude(); + + // std::cout << "Vertex Distance Check: " << start_v->wcpt().point << " to front " << segment_wcpts.front().point << " = " << dist_start_front/units::cm << " cm, to back " << segment_wcpts.back().point << " = " << dist_start_back/units::cm << " cm" << std::endl; + + // If start_v is closer to the back, swap + if (dist_start_back < dist_start_front) { std::swap(start_v, end_v); std::swap(vd1, vd2); } @@ -883,8 +890,14 @@ bool TrackFitting::get_ordered_segment_vertices( const auto& segment_wcpts = segment->wcpts(); if (segment_wcpts.empty()) return false; - // Check vertex ordering by comparing with segment endpoints - if (start_v->wcpt().index != segment_wcpts.front().index) { + // Use wcpt().point to determine which vertex matches the segment's front/back + double dist_start_front = (start_v->wcpt().point - segment_wcpts.front().point).magnitude(); + double dist_start_back = (start_v->wcpt().point - segment_wcpts.back().point).magnitude(); + + // std::cout << "Vertex Distance Check 1: " << start_v->wcpt().point << " to front " << segment_wcpts.front().point << " = " << dist_start_front/units::cm << " cm, to back " << segment_wcpts.back().point << " = " << dist_start_back/units::cm << " cm" << std::endl; + + // If start_v is closer to the back, swap + if (dist_start_back < dist_start_front) { std::swap(start_v, end_v); std::swap(vd1, vd2); } @@ -1259,6 +1272,9 @@ void TrackFitting::organize_segments_path(double low_dis_limit, double end_point WireCell::Point start_p, end_p; + start_v->fit().index = -1; + end_v->fit().index = -1; + // Process start vertex if (!start_v->fit().flag_fix) { start_p = temp_wcps_vec.front(); @@ -1370,6 +1386,9 @@ void TrackFitting::organize_segments_path(double low_dis_limit, double end_point // Generate 2D projections and store fit points in the segment segment->fits(generate_fits_with_projections(segment, pts)); } + + + } std::vector TrackFitting::organize_orig_path(std::shared_ptr segment, double low_dis_limit, double end_point_limit) { @@ -2734,13 +2753,10 @@ void TrackFitting::form_map_graph(bool flag_exclusion, double end_point_factor, std::shared_ptr start_v = nullptr, end_v = nullptr; if (v_bundle1.vertex && v_bundle2.vertex) { - // Determine which vertex is start and which is end by comparing with first/last fit points - auto& first_fit = fits.front(); - auto& last_fit = fits.back(); - - double dist1_first = (v_bundle1.vertex->fit().point - first_fit.point).magnitude(); - double dist1_last = (v_bundle1.vertex->fit().point - last_fit.point).magnitude(); - + // Determine which vertex is start and which is end by comparing with first/last fit points + double dist1_first = (v_bundle1.vertex->wcpt().point - segment->wcpts().front().point).magnitude(); + double dist1_last = (v_bundle1.vertex->wcpt().point - segment->wcpts().back().point).magnitude(); + if (dist1_first < dist1_last) { start_v = v_bundle1.vertex; end_v = v_bundle2.vertex; @@ -2756,9 +2772,10 @@ void TrackFitting::form_map_graph(bool flag_exclusion, double end_point_factor, distances.push_back((fits[i+1].point - fits[i].point).magnitude()); } - std::vector saved_pts; - std::vector saved_index; - std::vector saved_skip; + // std::vector saved_pts; + // std::vector saved_index; + // std::vector saved_skip; + std::vector saved_fits; // Process each fit point for (size_t i = 0; i < fits.size(); i++) { @@ -2817,28 +2834,35 @@ void TrackFitting::form_map_graph(bool flag_exclusion, double end_point_factor, m_2d_to_3d[coord].insert(count); } - saved_pts.push_back(fits[i].point); - saved_index.push_back(count); - saved_skip.push_back(false); + saved_fits.push_back(fits[i]); + saved_fits.back().index = count; + saved_fits.back().flag_fix = false; + count++; } } else if (i == 0) { // First point - saved_pts.push_back(fits[i].point); - saved_index.push_back(count); - saved_skip.push_back(true); - if (start_v && start_v->fit_index() == -1) { + saved_fits.push_back(fits[i]); + saved_fits.back().flag_fix = true; + if (start_v->fit_index() == -1) { start_v->fit_index(count); + saved_fits.back().index = count; count++; + }else{ + saved_fits.back().index = start_v->fit_index(); + // saved_index.push_back(start_v->fit_index()); + } } else if (i + 1 == fits.size()) { // Last point - saved_pts.push_back(fits[i].point); - saved_index.push_back(count); - saved_skip.push_back(true); - if (end_v && end_v->fit_index() == -1) { + saved_fits.push_back(fits[i]); + saved_fits.back().flag_fix = true; + if (end_v->fit_index() == -1) { end_v->fit_index(count); + saved_fits.back().index = count; count++; + }else{ + saved_fits.back().index = end_v->fit_index(); } } } @@ -2847,7 +2871,7 @@ void TrackFitting::form_map_graph(bool flag_exclusion, double end_point_factor, // Set fit associate vector for the segment - segment->set_fit_associate_vec(saved_pts, saved_index, saved_skip, m_dv, "fit"); + segment->set_fit_associate_vec(saved_fits, m_dv, "fit"); } // Deal with all vertices again @@ -3435,7 +3459,7 @@ void TrackFitting::multi_trajectory_fit(int charge_div_method, double div_sigma) // Update vertex with fitted position and projections auto& vertex_fit = vertex->fit(); vertex_fit.point = fitted_p; - vertex_fit.index = i; + vertex_fit.index = -1; // Store 2D projections (following WCP pattern with offsets) auto fitted_p_raw = transform->backward(fitted_p, cluster_t0, test_wpid.apa(), test_wpid.face()); @@ -3468,9 +3492,11 @@ void TrackFitting::multi_trajectory_fit(int charge_div_method, double div_sigma) std::shared_ptr start_v = nullptr, end_v = nullptr; if (v_bundle1.vertex && v_bundle2.vertex) { - // Determine which vertex is start and which is end based on fit indices - if (v_bundle1.vertex->fit_index() == 0 || - (v_bundle1.vertex->fit_index() < v_bundle2.vertex->fit_index() && v_bundle1.vertex->fit_index() >= 0)) { + // Determine which vertex is start and which is end by comparing with first/last fit points + double dist1_first = (v_bundle1.vertex->wcpt().point - segment->wcpts().front().point).magnitude(); + double dist1_last = (v_bundle1.vertex->wcpt().point - segment->wcpts().back().point).magnitude(); + + if (dist1_first < dist1_last) { start_v = v_bundle1.vertex; end_v = v_bundle2.vertex; } else { @@ -3557,6 +3583,8 @@ void TrackFitting::multi_trajectory_fit(int charge_div_method, double div_sigma) // Calculate 2D projections auto test_wpid = m_dv->contained_by(examined_ps[i]); + + if (test_wpid.apa() != -1 && test_wpid.face() != -1) { WirePlaneId wpid(kAllLayers, test_wpid.face(), test_wpid.apa()); auto offset_it = wpid_offsets.find(wpid); @@ -4808,7 +4836,7 @@ void TrackFitting::recover_original_charge_data(){ m_orig_charge_data.clear(); } -std::vector> TrackFitting::calculate_compact_matrix_multi(std::vector >& connected_vec,Eigen::SparseMatrix& weight_matrix, const Eigen::SparseMatrix& response_matrix_transpose, int n_2d_measurements, int n_3d_positions, double cut_position){ +std::vector> TrackFitting::calculate_compact_matrix_multi(std::vector >& connected_vec,Eigen::SparseMatrix& weight_matrix, const Eigen::SparseMatrix& response_matrix_transpose, int n_2d_measurements, int n_3d_positions, double cut_position){ // Initialize results vector - returns sharing ratios for each 3D position std::vector> results(n_3d_positions); @@ -4944,17 +4972,8 @@ std::vector> TrackFitting::calculate_compact_matrix_mu } } - // Convert to the expected return type (pair format for compatibility) - // Note: The WCP version returns vector>, but our signature expects vector> - // We'll return the first two sharing ratios as a pair, or (0,0) if less than 2 connections - std::vector> pair_results(results.size()); - for (size_t i = 0; i < results.size(); i++) { - double first = (results[i].size() > 0) ? results[i][0] : 0.0; - double second = (results[i].size() > 1) ? results[i][1] : 0.0; - pair_results[i] = std::make_pair(first, second); - } - - return pair_results; + // Return the 2D vector directly (consistent with WCPPID) + return results; } @@ -5208,6 +5227,11 @@ void TrackFitting::dQ_dx_fill(double dis_end_point_ext) { void TrackFitting::dQ_dx_multi_fit(double dis_end_point_ext, bool flag_dQ_dx_fit_reg){ if (!m_graph) return; + // Clear output vectors + dQ.clear(); + dx.clear(); + reduced_chi2.clear(); + // Update charge data for shared wires update_dQ_dx_data(); @@ -5262,6 +5286,8 @@ void TrackFitting::dQ_dx_multi_fit(double dis_end_point_ext, bool flag_dQ_dx_fit // Count total 3D positions from all segments and vertices int n_3D_pos = 0; + // reset fit_index for all vertices and segment points + std::map, int> vertex_index_map; std::map, int>, int> segment_point_index_map; @@ -5282,8 +5308,9 @@ void TrackFitting::dQ_dx_multi_fit(double dis_end_point_ext, bool flag_dQ_dx_fit auto& v_bundle2 = (*m_graph)[vd2]; std::shared_ptr start_v = nullptr, end_v = nullptr; + // std::cout << "Check: " << v_bundle1.vertex->fit().index << " " << v_bundle2.vertex->fit().index << " " << fits.front().index << " " << fits.back().index << " " << fits.size() << std::endl; if (v_bundle1.vertex && v_bundle2.vertex) { - if (v_bundle1.vertex->fit().index <= v_bundle2.vertex->fit().index) { + if (v_bundle1.vertex->fit().index == fits.front().index) { start_v = v_bundle1.vertex; end_v = v_bundle2.vertex; } else { @@ -5294,33 +5321,40 @@ void TrackFitting::dQ_dx_multi_fit(double dis_end_point_ext, bool flag_dQ_dx_fit // Assign indices to points for (size_t i = 0; i < fits.size(); i++) { + int idx = fits[i].index; if (i == 0) { // Start vertex if (start_v && vertex_index_map.find(start_v) == vertex_index_map.end()) { vertex_index_map[start_v] = start_v->fit().index; + idx = start_v->fit().index; } if (start_v) { segment_point_index_map[std::make_pair(segment, i)] = vertex_index_map[start_v]; } else { - segment_point_index_map[std::make_pair(segment, i)] = segment->fits()[i].index; + segment_point_index_map[std::make_pair(segment, i)] = idx; } } else if (i + 1 == fits.size()) { // End vertex if (end_v && vertex_index_map.find(end_v) == vertex_index_map.end()) { vertex_index_map[end_v] = end_v->fit().index; + idx = end_v->fit().index; } if (end_v) { segment_point_index_map[std::make_pair(segment, i)] = vertex_index_map[end_v]; } else { - segment_point_index_map[std::make_pair(segment, i)] = segment->fits()[i].index; + segment_point_index_map[std::make_pair(segment, i)] = idx; } } else { // Middle points - segment_point_index_map[std::make_pair(segment, i)] = segment->fits()[i].index; + segment_point_index_map[std::make_pair(segment, i)] = idx; } + // Track maximum index to determine n_3D_pos + if (idx + 1 > n_3D_pos) n_3D_pos = idx + 1; } } + // std::cout << n_3D_pos << " 3D positions identified for dQ/dx fitting." << std::endl; + if (n_3D_pos == 0) return; int n_2D_u = map_U_charge_2D.size(); @@ -5344,7 +5378,7 @@ void TrackFitting::dQ_dx_multi_fit(double dis_end_point_ext, bool flag_dQ_dx_fit // Initialize solution vector Eigen::VectorXd pos_3D_init(n_3D_pos); for (int i = 0; i < n_3D_pos; i++) { - pos_3D_init(i) = 50000.0; // Initial guess for single MIP + pos_3D_init(i) = 5000.0*6; // Initial guess for single MIP } // Fill data vectors with charge/uncertainty ratios @@ -5391,8 +5425,8 @@ void TrackFitting::dQ_dx_multi_fit(double dis_end_point_ext, bool flag_dQ_dx_fit n_w++; } } - - // Fill trajectory points and calculate dx values + + // Build response matrices using cal_gaus_integral_seg for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { auto& edge_bundle = (*m_graph)[*e_it]; if (!edge_bundle.segment) continue; @@ -5401,7 +5435,15 @@ void TrackFitting::dQ_dx_multi_fit(double dis_end_point_ext, bool flag_dQ_dx_fit auto& fits = segment->fits(); if (fits.empty()) continue; - // Fill trajectory points + // Get time ticks from cluster + int cur_ntime_ticks = 10; // Default value, should be calculated from cluster + if (edge_bundle.segment && edge_bundle.segment->cluster()) { + auto cluster = edge_bundle.segment->cluster(); + auto first_blob = cluster->children()[0]; + cur_ntime_ticks = first_blob->slice_index_max() - first_blob->slice_index_min(); + } + + // Fill trajectory points for (size_t i = 0; i < fits.size(); i++) { int idx = segment_point_index_map[std::make_pair(segment, i)]; traj_pts[idx] = fits[i].point; @@ -5421,69 +5463,7 @@ void TrackFitting::dQ_dx_multi_fit(double dis_end_point_ext, bool flag_dQ_dx_fit double dx = (curr_pos - prev_mid).magnitude() + (curr_pos - next_mid).magnitude(); local_dx[idx] = dx; } - } - - // Calculate dx for vertices (endpoints) - for (const auto& [vertex, vertex_idx] : vertex_index_map) { - std::vector connected_pts; - - // Find connected segments - auto vertex_desc = vertex->get_descriptor(); - if (vertex_desc != PR::Graph::null_vertex()) { - auto adj_edges = boost::adjacent_vertices(vertex_desc, *m_graph); - for (auto v_it = adj_edges.first; v_it != adj_edges.second; ++v_it) { - auto edge_desc = boost::edge(vertex_desc, *v_it, *m_graph); - if (edge_desc.second) { - auto& edge_bundle = (*m_graph)[edge_desc.first]; - if (edge_bundle.segment && !edge_bundle.segment->fits().empty()) { - auto& fits = edge_bundle.segment->fits(); - if (fits.size() > 1) { - // Add second point from segment - connected_pts.push_back(fits[1].point); - } - } - } - } - } - - // If only one connection, extend endpoint - if (connected_pts.size() == 1) { - WireCell::Point curr_pos = vertex->fit().point; - WireCell::Vector dir = (connected_pts[0] - curr_pos).norm(); - WireCell::Point extended = curr_pos - dir * dis_end_point_ext; - connected_pts.push_back(extended); - } - - // Calculate total dx - double total_dx = 0; - for (const auto& pt : connected_pts) { - total_dx += (pt - vertex->fit().point).magnitude(); - } - local_dx[vertex_idx] = total_dx; - } - - // Get time ticks from cluster - int cur_ntime_ticks = 10; // Default value, should be calculated from cluster - auto edge_range_temp = boost::edges(*m_graph); - for (auto e_it = edge_range_temp.first; e_it != edge_range_temp.second; ++e_it) { - auto& edge_bundle = (*m_graph)[*e_it]; - if (edge_bundle.segment && edge_bundle.segment->cluster()) { - auto cluster = edge_bundle.segment->cluster(); - auto first_blob = cluster->children()[0]; - cur_ntime_ticks = first_blob->slice_index_max() - first_blob->slice_index_min(); - break; - } - } - - // Build response matrices using cal_gaus_integral_seg - for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { - auto& edge_bundle = (*m_graph)[*e_it]; - if (!edge_bundle.segment) continue; - - auto segment = edge_bundle.segment; - auto& fits = segment->fits(); - if (fits.empty()) continue; - + // Process middle points for (size_t i = 1; i + 1 < fits.size(); i++) { int idx = segment_point_index_map[std::make_pair(segment, i)]; @@ -5491,45 +5471,59 @@ void TrackFitting::dQ_dx_multi_fit(double dis_end_point_ext, bool flag_dQ_dx_fit WireCell::Point prev_pos = fits[i-1].point; WireCell::Point curr_pos = fits[i].point; WireCell::Point next_pos = fits[i+1].point; - + // Create sampling points for Gaussian integration std::vector centers_U, centers_V, centers_W, centers_T; std::vector sigmas_T, sigmas_U, sigmas_V, sigmas_W; std::vector weights; + // Get geometry parameters + auto test_wpid = m_dv->contained_by(curr_pos); + if (test_wpid.apa() == -1 || test_wpid.face() == -1) continue; + int apa = test_wpid.apa(); + int face = test_wpid.face(); + + WirePlaneId wpid(kAllLayers, test_wpid.face(), test_wpid.apa()); + auto offset_it = wpid_offsets.find(wpid); + auto slope_it = wpid_slopes.find(wpid); + auto geom_it = wpid_geoms.find(wpid); + + if (offset_it == wpid_offsets.end() || slope_it == wpid_slopes.end() || geom_it == wpid_geoms.end()) continue; + auto cluster = segment->cluster(); + const auto transform = m_pcts->pc_transform(cluster->get_scope_transform(cluster->get_default_scope())); + double cluster_t0 = cluster->get_cluster_t0(); + + auto offset_t = std::get<0>(offset_it->second); + auto offset_u = std::get<1>(offset_it->second); + auto offset_v = std::get<2>(offset_it->second); + auto offset_w = std::get<3>(offset_it->second); + auto slope_x = std::get<0>(slope_it->second); + auto slope_yu = std::get<1>(slope_it->second).first; + auto slope_zu = std::get<1>(slope_it->second).second; + auto slope_yv = std::get<2>(slope_it->second).first; + auto slope_zv = std::get<2>(slope_it->second).second; + auto slope_yw = std::get<3>(slope_it->second).first; + auto slope_zw = std::get<3>(slope_it->second).second; + + auto time_tick_width = std::get<0>(geom_it->second); + auto pitch_u = std::get<1>(geom_it->second); + auto pitch_v = std::get<2>(geom_it->second); + auto pitch_w = std::get<3>(geom_it->second); + + // Get anode and tick information for drift time calculation + auto anode = m_grouping->get_anode(test_wpid.apa()); + auto iface = anode->faces()[test_wpid.face()]; + // double xsign = iface->dirx(); + double xorig = iface->planes()[2]->wires().front()->center().x(); // Anode plane position + // double tick_size = m_grouping->get_tick().at(test_wpid.apa()).at(test_wpid.face()); + double drift_speed = m_grouping->get_drift_speed().at(test_wpid.apa()).at(test_wpid.face()); + // Sample 5 points each from prev->curr and curr->next for (int j = 0; j < 5; j++) { // First half: prev -> curr WireCell::Point reco_pos = prev_pos + (curr_pos - prev_pos) * (j + 0.5) / 5.0; - - // Get geometry parameters - auto test_wpid = m_dv->contained_by(reco_pos); - if (test_wpid.apa() == -1 || test_wpid.face() == -1) continue; - - WirePlaneId wpid(kAllLayers, test_wpid.face(), test_wpid.apa()); - auto offset_it = wpid_offsets.find(wpid); - auto slope_it = wpid_slopes.find(wpid); - auto geom_it = wpid_geoms.find(wpid); - - if (offset_it == wpid_offsets.end() || slope_it == wpid_slopes.end() || geom_it == wpid_geoms.end()) continue; - - auto cluster = segment->cluster(); - const auto transform = m_pcts->pc_transform(cluster->get_scope_transform(cluster->get_default_scope())); - double cluster_t0 = cluster->get_cluster_t0(); auto reco_pos_raw = transform->backward(reco_pos, cluster_t0, test_wpid.face(), test_wpid.apa()); - auto offset_t = std::get<0>(offset_it->second); - auto offset_u = std::get<1>(offset_it->second); - auto offset_v = std::get<2>(offset_it->second); - auto offset_w = std::get<3>(offset_it->second); - auto slope_x = std::get<0>(slope_it->second); - auto slope_yu = std::get<1>(slope_it->second).first; - auto slope_zu = std::get<1>(slope_it->second).second; - auto slope_yv = std::get<2>(slope_it->second).first; - auto slope_zv = std::get<2>(slope_it->second).second; - auto slope_yw = std::get<3>(slope_it->second).first; - auto slope_zw = std::get<3>(slope_it->second).second; - double central_T = offset_t + slope_x * reco_pos_raw.x(); double central_U = offset_u + (slope_yu * reco_pos_raw.y() + slope_zu * reco_pos_raw.z()); double central_V = offset_v + (slope_yv * reco_pos_raw.y() + slope_zv * reco_pos_raw.z()); @@ -5537,18 +5531,12 @@ void TrackFitting::dQ_dx_multi_fit(double dis_end_point_ext, bool flag_dQ_dx_fit double weight = (prev_pos - curr_pos).magnitude(); - // Calculate diffusion sigmas (simplified - would need flash time in full implementation) - auto time_tick_width = std::get<0>(geom_it->second); - double drift_time = std::max(50.0 * units::microsecond, - reco_pos_raw.x() / time_tick_width * 0.5 * units::microsecond); - + // Calculate drift time from drift distance + double drift_distance = std::abs(reco_pos.x() - xorig); + double drift_time = std::max(m_params.min_drift_time, drift_distance / drift_speed); double diff_sigma_L = sqrt(2 * DL * drift_time); double diff_sigma_T = sqrt(2 * DT * drift_time); - auto pitch_u = std::get<1>(geom_it->second); - auto pitch_v = std::get<2>(geom_it->second); - auto pitch_w = std::get<3>(geom_it->second); - double sigma_L = sqrt(pow(diff_sigma_L, 2) + pow(add_sigma_L, 2)) / time_tick_width; double sigma_T_u = sqrt(pow(diff_sigma_T, 2) + pow(ind_sigma_u_T, 2)) / pitch_u; double sigma_T_v = sqrt(pow(diff_sigma_T, 2) + pow(ind_sigma_v_T, 2)) / pitch_v; @@ -5566,16 +5554,53 @@ void TrackFitting::dQ_dx_multi_fit(double dis_end_point_ext, bool flag_dQ_dx_fit // Second half: curr -> next reco_pos = next_pos + (curr_pos - next_pos) * (j + 0.5) / 5.0; - // ... (repeat similar calculations for second half) + reco_pos_raw = transform->backward(reco_pos, cluster_t0, test_wpid.face(), test_wpid.apa()); + + central_T = offset_t + slope_x * reco_pos_raw.x(); + central_U = offset_u + (slope_yu * reco_pos_raw.y() + slope_zu * reco_pos_raw.z()); + central_V = offset_v + (slope_yv * reco_pos_raw.y() + slope_zv * reco_pos_raw.z()); + central_W = offset_w + (slope_yw * reco_pos_raw.y() + slope_zw * reco_pos_raw.z()); + + weight = (next_pos - curr_pos).magnitude(); + + // Calculate drift time from drift distance + drift_distance = std::abs(reco_pos.x() - xorig); + drift_time = std::max(m_params.min_drift_time, drift_distance / drift_speed); + + diff_sigma_L = sqrt(2 * DL * drift_time); + diff_sigma_T = sqrt(2 * DT * drift_time); + + + sigma_L = sqrt(pow(diff_sigma_L, 2) + pow(add_sigma_L, 2)) / time_tick_width; + sigma_T_u = sqrt(pow(diff_sigma_T, 2) + pow(ind_sigma_u_T, 2)) / pitch_u; + sigma_T_v = sqrt(pow(diff_sigma_T, 2) + pow(ind_sigma_v_T, 2)) / pitch_v; + sigma_T_w = sqrt(pow(diff_sigma_T, 2) + pow(col_sigma_w_T, 2)) / pitch_w; + + centers_U.push_back(central_U); + centers_V.push_back(central_V); + centers_W.push_back(central_W); + centers_T.push_back(central_T); + weights.push_back(weight); + sigmas_U.push_back(sigma_T_u); + sigmas_V.push_back(sigma_T_v); + sigmas_W.push_back(sigma_T_w); + sigmas_T.push_back(sigma_L); } - // Fill response matrices using Gaussian integrals + // Fill response matrices using Gaussian integrals - U plane int n_u = 0; + std::set> set_UT; + for (const auto& [coord_key, result] : map_U_charge_2D) { + // const auto& measurement = result.first; const auto& coord_2d_set = result.second; for (const auto& coord_2d : coord_2d_set) { - if (abs(coord_2d.wire - centers_U.front()) <= 10 && - abs(coord_2d.time - centers_T.front()) <= 10) { + if (coord_2d.plane != kUlayer || coord_2d.apa != apa || coord_2d.face != face) continue; + int wire = coord_2d.wire; + int time = coord_2d.time; + set_UT.insert(std::make_pair(wire, time)); + + if (abs(wire - centers_U.front()) <= m_params.search_range && abs(time - centers_T.front()) <= m_params.search_range * cur_ntime_ticks) { double value = cal_gaus_integral_seg(coord_2d.time, coord_2d.wire, centers_T, sigmas_T, centers_U, sigmas_U, weights, 0, 4, cur_ntime_ticks); @@ -5589,14 +5614,456 @@ void TrackFitting::dQ_dx_multi_fit(double dis_end_point_ext, bool flag_dQ_dx_fit RU.insert(n_u, idx) = value / total_err; } } - break; // Only process first coord_2d for now } n_u++; } - // Similar processing for V and W planes... + // Fill response matrices using Gaussian integrals - V plane + int n_v = 0; + std::set> set_VT; + for (const auto& [coord_key, result] : map_V_charge_2D) { + const auto& coord_2d_set = result.second; + for (const auto& coord_2d : coord_2d_set) { + if (coord_2d.plane != kVlayer || coord_2d.apa != apa || coord_2d.face != face) continue; + int wire = coord_2d.wire; + int time = coord_2d.time; + set_VT.insert(std::make_pair(wire, time)); + if (abs(wire - centers_V.front()) <= m_params.search_range && abs(time - centers_T.front()) <= m_params.search_range * cur_ntime_ticks) { + + double value = cal_gaus_integral_seg(coord_2d.time, coord_2d.wire, centers_T, sigmas_T, + centers_V, sigmas_V, weights, 0, 4, cur_ntime_ticks); + + if (result.first.flag == 0 && value > 0) reg_flag_v[idx] = 1; + + if (value > 0 && result.first.charge > 0 && result.first.flag != 0) { + double total_err = sqrt(pow(result.first.charge_err, 2) + + pow(result.first.charge * rel_uncer_ind, 2) + + pow(add_uncer_ind, 2)); + RV.insert(n_v, idx) = value / total_err; + } + } + } + n_v++; + } + + // Fill response matrices using Gaussian integrals - W plane + int n_w = 0; + std::set> set_WT; + for (const auto& [coord_key, result] : map_W_charge_2D) { + const auto& coord_2d_set = result.second; + for (const auto& coord_2d : coord_2d_set) { + if (coord_2d.plane != kWlayer || coord_2d.apa != apa || coord_2d.face != face) continue; + int wire = coord_2d.wire; + int time = coord_2d.time; + set_WT.insert(std::make_pair(wire, time)); + if (abs(wire - centers_W.front()) <= m_params.search_range && abs(time - centers_T.front()) <= m_params.search_range * cur_ntime_ticks) { + + double value = cal_gaus_integral_seg(time, wire, centers_T, sigmas_T, + centers_W, sigmas_W, weights, 0, 4, cur_ntime_ticks); + + if (result.first.flag == 0 && value > 0) reg_flag_w[idx] = 1; + + if (value > 0 && result.first.charge > 0 && result.first.flag != 0) { + double total_err = sqrt(pow(result.first.charge_err, 2) + + pow(result.first.charge * rel_uncer_col, 2) + + pow(add_uncer_col, 2)); + RW.insert(n_w, idx) = value / total_err; + } + } + } + n_w++; + } + + // Additional checks on dead channels for segments + if (reg_flag_u[idx] == 0) { + for (size_t kk = 0; kk < centers_U.size(); kk++) { + if (set_UT.find(std::make_pair(std::round(centers_U[kk]), std::round(centers_T[kk]/cur_ntime_ticks)*cur_ntime_ticks)) == set_UT.end()) { + reg_flag_u[idx] = 1; + break; + } + } + } + + if (reg_flag_v[idx] == 0) { + for (size_t kk = 0; kk < centers_V.size(); kk++) { + if (set_VT.find(std::make_pair(std::round(centers_V[kk]), std::round(centers_T[kk]/cur_ntime_ticks)*cur_ntime_ticks)) == set_VT.end()) { + reg_flag_v[idx] = 1; + break; + } + } + } + + if (reg_flag_w[idx] == 0) { + for (size_t kk = 0; kk < centers_W.size(); kk++) { + if (set_WT.find(std::make_pair(std::round(centers_W[kk]), std::round(centers_T[kk]/cur_ntime_ticks)*cur_ntime_ticks)) == set_WT.end()) { + reg_flag_w[idx] = 1; + break; + } + } + } } } + + + // Calculate dx for vertices (endpoints) + for (const auto& [vertex, vertex_idx] : vertex_index_map) { + std::vector connected_pts; + + // Find connected segments + auto vertex_desc = vertex->get_descriptor(); + if (vertex_desc != PR::Graph::null_vertex()) { + auto adj_edges = boost::adjacent_vertices(vertex_desc, *m_graph); + for (auto v_it = adj_edges.first; v_it != adj_edges.second; ++v_it) { + auto edge_desc = boost::edge(vertex_desc, *v_it, *m_graph); + if (edge_desc.second) { + auto& edge_bundle = (*m_graph)[edge_desc.first]; + if (edge_bundle.segment && !edge_bundle.segment->fits().empty()) { + auto& fits = edge_bundle.segment->fits(); + if (fits.size() > 1){ + + // std::cout << vertex->fit().point << " " << vertex->fit().index << " " << fits.front().index << " " << fits.back().index << " " << vertex->fit().paf << " " << fits.front().paf << " " << fits.back().paf << " " << (vertex->fit().paf == fits[1].paf) << " " << (vertex->fit().paf == fits[fits.size() - 2].paf) << std::endl; + + if (vertex->fit().index == fits.front().index) { + // Start point + if (vertex->fit().paf == fits[1].paf) connected_pts.push_back(fits[1].point); + } else { + // End point + if (vertex->fit().paf == fits[fits.size() - 2].paf) connected_pts.push_back(fits[fits.size() - 2].point); + } + } + } + } + } + } + + // std::cout << connected_pts.size() << std::endl; + + // If only one connection, extend endpoint + if (connected_pts.size() == 1) { + WireCell::Point curr_pos = vertex->fit().point; + WireCell::Vector dir = (connected_pts[0] - curr_pos).norm(); + WireCell::Point extended = curr_pos - dir * dis_end_point_ext; + connected_pts[0] = extended; + } + + // Calculate total dx + double total_dx = 0; + for (const auto& pt : connected_pts) { + // std::cout << "Vertex connected point: (" << pt.x() << ", " << pt.y() << ", " << pt.z() << ")" << " " << (pt - vertex->fit().point).magnitude()/units::cm << std::endl; + total_dx += (pt - vertex->fit().point).magnitude(); + } + local_dx[vertex_idx] = total_dx; + } + + // // Process vertices (endpoints) + // for (const auto& [vertex, vertex_idx] : vertex_index_map) { + // WireCell::Point curr_pos = vertex->fit().point; + + // // Build list of connected segment points + // std::vector connected_pts; + // auto vertex_desc = vertex->get_descriptor(); + // if (vertex_desc != PR::Graph::null_vertex()) { + // auto out_edges = boost::out_edges(vertex_desc, *m_graph); + // for (auto e_it = out_edges.first; e_it != out_edges.second; ++e_it) { + // auto& edge_bundle = (*m_graph)[*e_it]; + // if (edge_bundle.segment && !edge_bundle.segment->fits().empty()) { + // auto& fits = edge_bundle.segment->fits(); + // if (fits.size() > 1) { + // WireCell::Point p = 0.5 * (curr_pos + fits[1].point); + // connected_pts.push_back(p); + // } + // } + // } + // } + + // // If only one connection, extend the endpoint + // if (connected_pts.size() == 1) { + // WireCell::Vector dir = (connected_pts[0] - curr_pos).norm(); + // WireCell::Point extended = curr_pos - dir * dis_end_point_ext; + // connected_pts.push_back(extended); + // } + + // if (connected_pts.empty()) continue; + + // // Build Gaussian centers and sigmas for vertex + // std::vector centers_U, centers_V, centers_W, centers_T; + // std::vector sigmas_T, sigmas_U, sigmas_V, sigmas_W; + // std::vector weights; + + // for (size_t k = 0; k < connected_pts.size(); k++) { + // for (int j = 0; j < 5; j++) { + // WireCell::Point reco_pos = connected_pts[k] + (curr_pos - connected_pts[k]) * (j + 0.5) / 5.0; + + // auto test_wpid = m_dv->contained_by(reco_pos); + // if (test_wpid.apa() == -1 || test_wpid.face() == -1) continue; + + // WirePlaneId wpid(kAllLayers, test_wpid.face(), test_wpid.apa()); + // auto offset_it = wpid_offsets.find(wpid); + // auto slope_it = wpid_slopes.find(wpid); + // auto geom_it = wpid_geoms.find(wpid); + + // if (offset_it == wpid_offsets.end() || slope_it == wpid_slopes.end() || geom_it == wpid_geoms.end()) continue; + + // // Get first segment's cluster for transform + // auto out_edges = boost::out_edges(vertex_desc, *m_graph); + // std::shared_ptr cluster = nullptr; + // for (auto e_it = out_edges.first; e_it != out_edges.second; ++e_it) { + // auto& edge_bundle = (*m_graph)[*e_it]; + // if (edge_bundle.segment && edge_bundle.segment->cluster()) { + // cluster = edge_bundle.segment->cluster(); + // break; + // } + // } + // if (!cluster) continue; + + // const auto transform = m_pcts->pc_transform(cluster->get_scope_transform(cluster->get_default_scope())); + // double cluster_t0 = cluster->get_cluster_t0(); + // auto reco_pos_raw = transform->backward(reco_pos, cluster_t0, test_wpid.face(), test_wpid.apa()); + + // auto offset_t = std::get<0>(offset_it->second); + // auto offset_u = std::get<1>(offset_it->second); + // auto offset_v = std::get<2>(offset_it->second); + // auto offset_w = std::get<3>(offset_it->second); + // auto slope_x = std::get<0>(slope_it->second); + // auto slope_yu = std::get<1>(slope_it->second).first; + // auto slope_zu = std::get<1>(slope_it->second).second; + // auto slope_yv = std::get<2>(slope_it->second).first; + // auto slope_zv = std::get<2>(slope_it->second).second; + // auto slope_yw = std::get<3>(slope_it->second).first; + // auto slope_zw = std::get<3>(slope_it->second).second; + + // double central_T = offset_t + slope_x * reco_pos_raw.x(); + // double central_U = offset_u + (slope_yu * reco_pos_raw.y() + slope_zu * reco_pos_raw.z()); + // double central_V = offset_v + (slope_yv * reco_pos_raw.y() + slope_zv * reco_pos_raw.z()); + // double central_W = offset_w + (slope_yw * reco_pos_raw.y() + slope_zw * reco_pos_raw.z()); + + // double weight = (connected_pts[k] - curr_pos).magnitude(); + + // auto time_tick_width = std::get<0>(geom_it->second); + // double drift_time = std::max(50.0 * units::microsecond, + // reco_pos_raw.x() / time_tick_width * 0.5 * units::microsecond); + + // double diff_sigma_L = sqrt(2 * DL * drift_time); + // double diff_sigma_T = sqrt(2 * DT * drift_time); + + // auto pitch_u = std::get<1>(geom_it->second); + // auto pitch_v = std::get<2>(geom_it->second); + // auto pitch_w = std::get<3>(geom_it->second); + + // double sigma_L = sqrt(pow(diff_sigma_L, 2) + pow(add_sigma_L, 2)) / time_tick_width; + // double sigma_T_u = sqrt(pow(diff_sigma_T, 2) + pow(ind_sigma_u_T, 2)) / pitch_u; + // double sigma_T_v = sqrt(pow(diff_sigma_T, 2) + pow(ind_sigma_v_T, 2)) / pitch_v; + // double sigma_T_w = sqrt(pow(diff_sigma_T, 2) + pow(col_sigma_w_T, 2)) / pitch_w; + + // centers_U.push_back(central_U); + // centers_V.push_back(central_V); + // centers_W.push_back(central_W); + // centers_T.push_back(central_T); + // weights.push_back(weight); + // sigmas_U.push_back(sigma_T_u); + // sigmas_V.push_back(sigma_T_v); + // sigmas_W.push_back(sigma_T_w); + // sigmas_T.push_back(sigma_L); + // } + // } + + // if (centers_U.empty()) continue; + + // // Fill response matrices for vertex - U plane + // int n_u = 0; + // for (const auto& [coord_key, result] : map_U_charge_2D) { + // const auto& coord_2d_set = result.second; + // for (const auto& coord_2d : coord_2d_set) { + // if (abs(coord_2d.wire - centers_U.front()) <= 10 && + // abs(coord_2d.time - centers_T.front()) <= 10) { + + // double value = cal_gaus_integral_seg(coord_2d.time, coord_2d.wire, centers_T, sigmas_T, + // centers_U, sigmas_U, weights, 0, 4, cur_ntime_ticks); + + // if (result.first.flag == 0 && value > 0) reg_flag_u[vertex_idx] = 1; + + // if (value > 0 && result.first.charge > 0 && result.first.flag != 0) { + // double total_err = sqrt(pow(result.first.charge_err, 2) + + // pow(result.first.charge * rel_uncer_ind, 2) + + // pow(add_uncer_ind, 2)); + // RU.insert(n_u, vertex_idx) = value / total_err; + // } + // } + // break; + // } + // n_u++; + // } + + // // Fill response matrices for vertex - V plane + // int n_v = 0; + // for (const auto& [coord_key, result] : map_V_charge_2D) { + // const auto& coord_2d_set = result.second; + // for (const auto& coord_2d : coord_2d_set) { + // if (abs(coord_2d.wire - centers_V.front()) <= 10 && + // abs(coord_2d.time - centers_T.front()) <= 10) { + + // double value = cal_gaus_integral_seg(coord_2d.time, coord_2d.wire, centers_T, sigmas_T, + // centers_V, sigmas_V, weights, 0, 4, cur_ntime_ticks); + + // if (result.first.flag == 0 && value > 0) reg_flag_v[vertex_idx] = 1; + + // if (value > 0 && result.first.charge > 0 && result.first.flag != 0) { + // double total_err = sqrt(pow(result.first.charge_err, 2) + + // pow(result.first.charge * rel_uncer_ind, 2) + + // pow(add_uncer_ind, 2)); + // RV.insert(n_v, vertex_idx) = value / total_err; + // } + // } + // break; + // } + // n_v++; + // } + + // // Fill response matrices for vertex - W plane + // int n_w = 0; + // for (const auto& [coord_key, result] : map_W_charge_2D) { + // const auto& coord_2d_set = result.second; + // for (const auto& coord_2d : coord_2d_set) { + // if (abs(coord_2d.wire - centers_W.front()) <= 10 && + // abs(coord_2d.time - centers_T.front()) <= 10) { + + // double value = cal_gaus_integral_seg(coord_2d.time, coord_2d.wire, centers_T, sigmas_T, + // centers_W, sigmas_W, weights, 0, 4, cur_ntime_ticks); + + // if (result.first.flag == 0 && value > 0) reg_flag_w[vertex_idx] = 1; + + // if (value > 0 && result.first.charge > 0 && result.first.flag != 0) { + // double total_err = sqrt(pow(result.first.charge_err, 2) + + // pow(result.first.charge * rel_uncer_col, 2) + + // pow(add_uncer_col, 2)); + // RW.insert(n_w, vertex_idx) = value / total_err; + // } + // } + // break; + // } + // n_w++; + // } + + // // Additional checks on dead channels for vertices + // if (reg_flag_u[vertex_idx] == 0) { + // for (size_t kk = 0; kk < centers_U.size(); kk++) { + // bool found = false; + // for (const auto& [coord_key, result] : map_U_charge_2D) { + // const auto& coord_2d_set = result.second; + // for (const auto& coord_2d : coord_2d_set) { + // if (abs(coord_2d.wire - centers_U[kk]) < 0.5 && abs(coord_2d.time - centers_T[kk]) < 0.5) { + // found = true; + // break; + // } + // } + // if (found) break; + // } + // if (!found) { + // reg_flag_u[vertex_idx] = 1; + // break; + // } + // } + // } + + // if (reg_flag_v[vertex_idx] == 0) { + // for (size_t kk = 0; kk < centers_V.size(); kk++) { + // bool found = false; + // for (const auto& [coord_key, result] : map_V_charge_2D) { + // const auto& coord_2d_set = result.second; + // for (const auto& coord_2d : coord_2d_set) { + // if (abs(coord_2d.wire - centers_V[kk]) < 0.5 && abs(coord_2d.time - centers_T[kk]) < 0.5) { + // found = true; + // break; + // } + // } + // if (found) break; + // } + // if (!found) { + // reg_flag_v[vertex_idx] = 1; + // break; + // } + // } + // } + + // if (reg_flag_w[vertex_idx] == 0) { + // for (size_t kk = 0; kk < centers_W.size(); kk++) { + // bool found = false; + // for (const auto& [coord_key, result] : map_W_charge_2D) { + // const auto& coord_2d_set = result.second; + // for (const auto& coord_2d : coord_2d_set) { + // if (abs(coord_2d.wire - centers_W[kk]) < 0.5 && abs(coord_2d.time - centers_T[kk]) < 0.5) { + // found = true; + // break; + // } + // } + // if (found) break; + // } + // if (!found) { + // reg_flag_w[vertex_idx] = 1; + // break; + // } + // } + // } + // } + + // // Copy local_dx into segment dx_vec for later use + // for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + // auto& edge_bundle = (*m_graph)[*e_it]; + // if (!edge_bundle.segment) continue; + + // auto segment = edge_bundle.segment; + // auto& fits = segment->fits(); + // if (fits.empty()) continue; + + // for (size_t i = 0; i < fits.size(); i++) { + // int idx = segment_point_index_map[std::make_pair(segment, i)]; + // if (idx < n_3D_pos) { + // fits[i].dx = local_dx[idx]; + // } + // } + // } + + // // Build connected_vec for regularization + // std::vector> connected_vec(n_3D_pos); + // for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + // auto& edge_bundle = (*m_graph)[*e_it]; + // if (!edge_bundle.segment) continue; + + // auto segment = edge_bundle.segment; + // auto& fits = segment->fits(); + // if (fits.empty()) continue; + + // for (size_t i = 1; i + 1 < fits.size(); i++) { + // int idx = segment_point_index_map[std::make_pair(segment, i)]; + // int prev_idx = segment_point_index_map[std::make_pair(segment, i-1)]; + // int next_idx = segment_point_index_map[std::make_pair(segment, i+1)]; + + // connected_vec[idx].push_back(prev_idx); + // connected_vec[idx].push_back(next_idx); + // } + // } + + // // Add vertex connections + // for (const auto& [vertex, vertex_idx] : vertex_index_map) { + // auto vertex_desc = vertex->get_descriptor(); + // if (vertex_desc != PR::Graph::null_vertex()) { + // auto out_edges = boost::out_edges(vertex_desc, *m_graph); + // for (auto e_it = out_edges.first; e_it != out_edges.second; ++e_it) { + // auto& edge_bundle = (*m_graph)[*e_it]; + // if (edge_bundle.segment && !edge_bundle.segment->fits().empty()) { + // auto& fits = edge_bundle.segment->fits(); + // // Connect vertex to second point of segment (first point is vertex itself) + // if (fits.size() > 1 && fits[0].index == vertex_idx) { + // connected_vec[vertex_idx].push_back(fits[1].index); + // } else if (fits.size() > 1 && fits.back().index == vertex_idx) { + // connected_vec[vertex_idx].push_back(fits[fits.size()-2].index); + // } + // } + // } + // } + // } + // } + // } // Build connected_vec for regularization std::vector> connected_vec(n_3D_pos); @@ -5680,15 +6147,13 @@ void TrackFitting::dQ_dx_multi_fit(double dis_end_point_ext, bool flag_dQ_dx_fit double scaling = (connected_vec[i].size() > 2) ? 2.0 / connected_vec[i].size() : 1.0; for (size_t j = 0; j < connected_vec[i].size(); j++) { - if (j >= overlap_u.size() || i >= overlap_u.size()) continue; - double weight1 = weight; int row = i; int col = connected_vec[i][j]; - if (overlap_u[i].first > 0.5) weight1 += close_ind_weight * pow(overlap_u[i].first - 0.5, 2); - if (overlap_v[i].first > 0.5) weight1 += close_ind_weight * pow(overlap_v[i].first - 0.5, 2); - if (overlap_w[i].first > 0.5) weight1 += close_col_weight * pow(overlap_w[i].first - 0.5, 2); + if (overlap_u[i][j] > 0.5) weight1 += close_ind_weight * pow(overlap_u[i][j] - 0.5, 2); + if (overlap_v[i][j] > 0.5) weight1 += close_ind_weight * pow(overlap_v[i][j] - 0.5, 2); + if (overlap_w[i][j] > 0.5) weight1 += close_col_weight * pow(overlap_w[i][j] - 0.5, 2); double dx_norm = (local_dx[row] + 0.001 * units::cm) / (0.6 * units::cm); FMatrix.coeffRef(row, row) += -weight1 * scaling / dx_norm; @@ -5724,6 +6189,63 @@ void TrackFitting::dQ_dx_multi_fit(double dis_end_point_ext, bool flag_dQ_dx_fit traj_reduced_chi2.clear(); traj_reduced_chi2.resize(n_3D_pos, 0.0); + std::vector traj_ndf(n_3D_pos, 0); + + // Calculate chi-squared contributions from U plane + for (int k = 0; k < RU.outerSize(); ++k) { + for (Eigen::SparseMatrix::InnerIterator it(RU, k); it; ++it) { + int row = it.row(); + int col = it.col(); + + double diff = pred_data_u_2D(row) - data_u_2D(row); + double error_sq = pow(rel_uncer_ind * data_u_2D(row), 2) + pow(add_uncer_ind, 2); + + if (error_sq > 0) { + traj_reduced_chi2[col] += pow(diff, 2) / error_sq; + traj_ndf[col]++; + } + } + } + + // Calculate chi-squared contributions from V plane + for (int k = 0; k < RV.outerSize(); ++k) { + for (Eigen::SparseMatrix::InnerIterator it(RV, k); it; ++it) { + int row = it.row(); + int col = it.col(); + + double diff = pred_data_v_2D(row) - data_v_2D(row); + double error_sq = pow(rel_uncer_ind * data_v_2D(row), 2) + pow(add_uncer_ind, 2); + + if (error_sq > 0) { + traj_reduced_chi2[col] += pow(diff, 2) / error_sq; + traj_ndf[col]++; + } + } + } + + // Calculate chi-squared contributions from W plane + for (int k = 0; k < RW.outerSize(); ++k) { + for (Eigen::SparseMatrix::InnerIterator it(RW, k); it; ++it) { + int row = it.row(); + int col = it.col(); + + double diff = pred_data_w_2D(row) - data_w_2D(row); + double error_sq = pow(rel_uncer_col * data_w_2D(row), 2) + pow(add_uncer_col, 2); + + if (error_sq > 0) { + traj_reduced_chi2[col] += pow(diff, 2) / error_sq; + traj_ndf[col]++; + } + } + } + + // Normalize chi2 by degrees of freedom + for (size_t i = 0; i < n_3D_pos; i++) { + if (traj_ndf[i] > 0) { + traj_reduced_chi2[i] /= traj_ndf[i]; + } + } + // Update vertex and segment fit results for (const auto& [vertex, vertex_idx] : vertex_index_map) { if (vertex_idx >= n_3D_pos) continue; @@ -5925,8 +6447,10 @@ void WireCell::Clus::TrackFitting::dQ_dx_fit(double dis_end_point_ext, bool flag for (int i = 0; i < n_3D_pos; i++) { WireCell::Point prev_rec_pos, next_rec_pos; WireCell::Point curr_rec_pos = fine_tracking_path.at(i).first; - - if (i == 0) { + + // if (i!=0) std::cout << i << " TTT " << (paf.at(i) == paf.at(i-1)) << std::endl; + + if (i == 0 || (i!=0 && paf.at(i) != paf.at(i-1))) { // First point: extrapolate backward if (n_3D_pos > 1) { WireCell::Point next_point = fine_tracking_path.at(i+1).first; @@ -5942,7 +6466,7 @@ void WireCell::Clus::TrackFitting::dQ_dx_fit(double dis_end_point_ext, bool flag prev_rec_pos = curr_rec_pos; next_rec_pos = curr_rec_pos; } - } else if (i == n_3D_pos - 1) { + } else if (i == n_3D_pos - 1 || (i!=n_3D_pos-1 && paf.at(i) != paf.at(i+1))) { // Last point: extrapolate forward WireCell::Point prev_point = fine_tracking_path.at(i-1).first; WireCell::Vector dir = curr_rec_pos - prev_point; @@ -5953,10 +6477,14 @@ void WireCell::Clus::TrackFitting::dQ_dx_fit(double dis_end_point_ext, bool flag next_rec_pos = curr_rec_pos; } prev_rec_pos = (curr_rec_pos + prev_point) * 0.5; - } else { + } else if (paf.at(i) == paf.at(i-1) && paf.at(i) == paf.at(i+1)){ // Middle point prev_rec_pos = (curr_rec_pos + fine_tracking_path.at(i-1).first) * 0.5; next_rec_pos = (curr_rec_pos + fine_tracking_path.at(i+1).first) * 0.5; + }else { + // Default case (should not happen) + prev_rec_pos = curr_rec_pos; + next_rec_pos = curr_rec_pos; } dx[i] = (curr_rec_pos - prev_rec_pos).magnitude() + (curr_rec_pos - next_rec_pos).magnitude(); @@ -6005,10 +6533,18 @@ void WireCell::Clus::TrackFitting::dQ_dx_fit(double dis_end_point_ext, bool flag double pitch_v = std::get<2>(geom_it->second); double pitch_w = std::get<3>(geom_it->second); + // Get anode and tick information for drift time calculation + auto anode = m_grouping->get_anode(apa); + auto iface = anode->faces()[face]; + // double xsign = iface->dirx(); + double xorig = iface->planes()[2]->wires().front()->center().x(); // Anode plane position + // double tick_size = m_grouping->get_tick().at(apa).at(face); + double drift_speed = m_grouping->get_drift_speed().at(apa).at(face); + // Calculate previous and next positions for Gaussian integration WireCell::Point prev_rec_pos, next_rec_pos; - if (i == 0) { + if (i == 0 || (i!=0 && paf.at(i) != paf.at(i-1))) { if (n_3D_pos > 1) { WireCell::Point next_point = fine_tracking_path.at(i+1).first; next_rec_pos = (curr_rec_pos + next_point) * 0.5; @@ -6022,7 +6558,7 @@ void WireCell::Clus::TrackFitting::dQ_dx_fit(double dis_end_point_ext, bool flag } else { prev_rec_pos = next_rec_pos = curr_rec_pos; } - } else if (i == n_3D_pos - 1) { + } else if (i == n_3D_pos - 1 || (i!=n_3D_pos-1 && paf.at(i) != paf.at(i+1))) { WireCell::Point prev_point = fine_tracking_path.at(i-1).first; prev_rec_pos = (curr_rec_pos + prev_point) * 0.5; WireCell::Vector dir = curr_rec_pos - prev_point; @@ -6032,9 +6568,12 @@ void WireCell::Clus::TrackFitting::dQ_dx_fit(double dis_end_point_ext, bool flag } else { next_rec_pos = curr_rec_pos; } - } else { + } else if (paf.at(i) == paf.at(i-1) && paf.at(i) == paf.at(i+1)) { prev_rec_pos = (curr_rec_pos + fine_tracking_path.at(i-1).first) * 0.5; next_rec_pos = (curr_rec_pos + fine_tracking_path.at(i+1).first) * 0.5; + }else{ + prev_rec_pos = curr_rec_pos; + next_rec_pos = curr_rec_pos; } // Create Gaussian integration points and weights @@ -6055,8 +6594,9 @@ void WireCell::Clus::TrackFitting::dQ_dx_fit(double dis_end_point_ext, bool flag double central_W = offset_w + (slope_yw * reco_pos_raw.y() + slope_zw * reco_pos_raw.z()); double weight = (curr_rec_pos - prev_rec_pos).magnitude(); - // Calculate drift time and diffusion - double drift_time = std::max(m_params.min_drift_time, reco_pos.x() / time_tick_width * 0.5*units::us ); + // Calculate drift time from drift distance + double drift_distance = std::abs(reco_pos.x() - xorig); + double drift_time = std::max(m_params.min_drift_time, drift_distance / drift_speed); double diff_sigma_L = sqrt(2 * DL * drift_time); double diff_sigma_T = sqrt(2 * DT * drift_time); @@ -6085,7 +6625,9 @@ void WireCell::Clus::TrackFitting::dQ_dx_fit(double dis_end_point_ext, bool flag central_W = offset_w + (slope_yw * reco_pos_raw.y() + slope_zw * reco_pos_raw.z()); weight = (curr_rec_pos - next_rec_pos).magnitude(); - drift_time = std::max(m_params.min_drift_time, reco_pos.x() / time_tick_width * 0.5*units::us ); + // Calculate drift time from drift distance + drift_distance = std::abs(reco_pos.x() - xorig); + drift_time = std::max(m_params.min_drift_time, drift_distance / drift_speed); diff_sigma_L = sqrt(2 * DL * drift_time); diff_sigma_T = sqrt(2 * DT * drift_time); @@ -6487,8 +7029,26 @@ void TrackFitting::do_multi_tracking(bool flag_dQ_dx_fit_reg, bool flag_dQ_dx_fi fill_global_rb_map(); } + // auto edge_range = boost::edges(*m_graph); + // for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + // auto& edge_bundle = (*m_graph)[*e_it]; + // if (edge_bundle.segment) { + // std::cout << "Segment fits size: " << edge_bundle.segment->fits().size() << std::endl; + // for (const auto& fit : edge_bundle.segment->fits()) { + // std::cout << " Fit point: " << fit.point << " " << fit.index << std::endl; + // } + // } + // } + // for (auto vp = boost::vertices(*m_graph); vp.first != vp.second; ++vp.first) { + // auto vd = *vp.first; + // auto& v_bundle = (*m_graph)[vd]; + // if (v_bundle.vertex) { + // std::cout << v_bundle.vertex->fit().point << " VTX " << v_bundle.vertex->fit().index << std::endl; + // } + // } + // for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { // auto& edge_bundle = (*m_graph)[*e_it]; // if (edge_bundle.segment) { @@ -6509,9 +7069,59 @@ void TrackFitting::do_multi_tracking(bool flag_dQ_dx_fit_reg, bool flag_dQ_dx_fi // std::cout << "Segment fits size: " << edge_bundle.segment->fits().size() << std::endl; // } // } + // for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + // auto& edge_bundle = (*m_graph)[*e_it]; + // if (edge_bundle.segment) { + // std::cout << "Segment fits size: " << edge_bundle.segment->fits().size() << std::endl; + // for (const auto& fit : edge_bundle.segment->fits()) { + // std::cout << " Fit point: " << fit.point << " " << fit.index << std::endl; + // } + // } + // } + // for (auto vp = boost::vertices(*m_graph); vp.first != vp.second; ++vp.first) { + // auto vd = *vp.first; + // auto& v_bundle = (*m_graph)[vd]; + // if (v_bundle.vertex) { + // std::cout << v_bundle.vertex->fit().point << " VTX " << v_bundle.vertex->fit().index << std::endl; + // } + // } if (flag_1st_tracking){ + + // auto edge_range = boost::edges(*m_graph); + // for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + // auto& edge_bundle = (*m_graph)[*e_it]; + // if (edge_bundle.segment) { + // std::cout << "Segment fits size: " << edge_bundle.segment->fits().size() << std::endl; + // for (const auto& fit : edge_bundle.segment->fits()) { + // std::cout << " Fit point: " << fit.point << " " << fit.index << std::endl; + // } + // } + // } + // for (auto vp = boost::vertices(*m_graph); vp.first != vp.second; ++vp.first) { + // auto vd = *vp.first; + // auto& v_bundle = (*m_graph)[vd]; + // if (v_bundle.vertex) { + // std::cout << v_bundle.vertex->fit().point << " VTX " << v_bundle.vertex->fit().index << std::endl; + // } + // } form_map_graph(flag_exclusion, m_params.end_point_factor, m_params.mid_point_factor, m_params.nlevel, m_params.time_tick_cut, m_params.charge_cut); + // for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + // auto& edge_bundle = (*m_graph)[*e_it]; + // if (edge_bundle.segment) { + // std::cout << "Segment fits size: " << edge_bundle.segment->fits().size() << std::endl; + // for (const auto& fit : edge_bundle.segment->fits()) { + // std::cout << " Fit point: " << fit.point << " " << fit.index << std::endl; + // } + // } + // } + // for (auto vp = boost::vertices(*m_graph); vp.first != vp.second; ++vp.first) { + // auto vd = *vp.first; + // auto& v_bundle = (*m_graph)[vd]; + // if (v_bundle.vertex) { + // std::cout << v_bundle.vertex->fit().point << " VTX " << v_bundle.vertex->fit().index << std::endl; + // } + // } multi_trajectory_fit(1, m_params.div_sigma); } @@ -6526,6 +7136,16 @@ void TrackFitting::do_multi_tracking(bool flag_dQ_dx_fit_reg, bool flag_dQ_dx_fi // auto& edge_bundle = (*m_graph)[*e_it]; // if (edge_bundle.segment) { // std::cout << "Segment fits size: " << edge_bundle.segment->fits().size() << std::endl; + // for (const auto& fit : edge_bundle.segment->fits()) { + // std::cout << " Fit point: " << fit.point << " " << fit.index << std::endl; + // } + // } + // } + // for (auto vp = boost::vertices(*m_graph); vp.first != vp.second; ++vp.first) { + // auto vd = *vp.first; + // auto& v_bundle = (*m_graph)[vd]; + // if (v_bundle.vertex) { + // std::cout << v_bundle.vertex->fit().point << " VTX " << v_bundle.vertex->fit().index << std::endl; // } // } @@ -6533,51 +7153,107 @@ void TrackFitting::do_multi_tracking(bool flag_dQ_dx_fit_reg, bool flag_dQ_dx_fi organize_segments_path_2nd(low_dis_limit, end_point_limit); // std::cout << "After second organization " << std::endl; - // for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + // for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { // auto& edge_bundle = (*m_graph)[*e_it]; // if (edge_bundle.segment) { // std::cout << "Segment fits size: " << edge_bundle.segment->fits().size() << std::endl; + // for (const auto& fit : edge_bundle.segment->fits()) { + // std::cout << " Fit point: " << fit.point << " " << fit.index << std::endl; + // } + // } + // } + // for (auto vp = boost::vertices(*m_graph); vp.first != vp.second; ++vp.first) { + // auto vd = *vp.first; + // auto& v_bundle = (*m_graph)[vd]; + // if (v_bundle.vertex) { + // std::cout << v_bundle.vertex->fit().point << " VTX " << v_bundle.vertex->fit().index << std::endl; // } // } form_map_graph(flag_exclusion, m_params.end_point_factor, m_params.mid_point_factor, m_params.nlevel, m_params.time_tick_cut, m_params.charge_cut); - multi_trajectory_fit(1, m_params.div_sigma); + // for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + // auto& edge_bundle = (*m_graph)[*e_it]; + // if (edge_bundle.segment) { + // std::cout << "Segment fits size: " << edge_bundle.segment->fits().size() << std::endl; + // for (const auto& fit : edge_bundle.segment->fits()) { + // std::cout << " Fit point: " << fit.point << " " << fit.index << " " << fit.paf.first << " " << fit.paf.second << std::endl; + // } + // } + // } + // for (auto vp = boost::vertices(*m_graph); vp.first != vp.second; ++vp.first) { + // auto vd = *vp.first; + // auto& v_bundle = (*m_graph)[vd]; + // if (v_bundle.vertex) { + // std::cout << v_bundle.vertex->fit().point << " VTX " << v_bundle.vertex->fit().index << " " << v_bundle.vertex->fit().paf.first << " " << v_bundle.vertex->fit().paf.second << std::endl; + // } + // } + // organize path low_dis_limit = 0.6*units::cm; organize_segments_path_3rd(low_dis_limit); - // std::cout << "After third organization " << std::endl; - // for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + // for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { // auto& edge_bundle = (*m_graph)[*e_it]; // if (edge_bundle.segment) { // std::cout << "Segment fits size: " << edge_bundle.segment->fits().size() << std::endl; + // for (const auto& fit : edge_bundle.segment->fits()) { + // std::cout << " Fit point: " << fit.point << " " << fit.index << " " << fit.paf.first << " " << fit.paf.second << std::endl; + // } + // } + // } + // for (auto vp = boost::vertices(*m_graph); vp.first != vp.second; ++vp.first) { + // auto vd = *vp.first; + // auto& v_bundle = (*m_graph)[vd]; + // if (v_bundle.vertex) { + // std::cout << v_bundle.vertex->fit().point << " VTX " << v_bundle.vertex->fit().index << " " << v_bundle.vertex->fit().paf.first << " " << v_bundle.vertex->fit().paf.second << std::endl; // } // } + } - // if (flag_dQ_dx){ - // for (auto vp = boost::vertices(*m_graph); vp.first != vp.second; ++vp.first) { - // auto vd = *vp.first; - // auto& v_bundle = (*m_graph)[vd]; - // if (v_bundle.vertex) { - // bool flag_fix = v_bundle.vertex->flag_fix(); - // v_bundle.vertex->reset_fit_prop(); - // v_bundle.vertex->flag_fix(flag_fix); - // } - // } + if (flag_dQ_dx){ + for (auto vp = boost::vertices(*m_graph); vp.first != vp.second; ++vp.first) { + auto vd = *vp.first; + auto& v_bundle = (*m_graph)[vd]; + if (v_bundle.vertex) { + bool flag_fix = v_bundle.vertex->flag_fix(); + v_bundle.vertex->reset_fit_prop(); + v_bundle.vertex->flag_fix(flag_fix); + } + } - // auto edge_range = boost::edges(*m_graph); - // for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { - // auto& edge_bundle = (*m_graph)[*e_it]; - // if (edge_bundle.segment) { - // edge_bundle.segment->reset_fit_prop(); - // } - // } - // dQ_dx_multi_fit(end_point_limit, flag_dQ_dx_fit_reg); - // } + auto edge_range = boost::edges(*m_graph); + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + auto& edge_bundle = (*m_graph)[*e_it]; + if (edge_bundle.segment) { + edge_bundle.segment->reset_fit_prop(); + } + } + form_map_graph(flag_exclusion, m_params.end_point_factor, m_params.mid_point_factor, m_params.nlevel, m_params.time_tick_cut, m_params.charge_cut); + + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + auto& edge_bundle = (*m_graph)[*e_it]; + if (edge_bundle.segment) { + std::cout << "Segment fits size: " << edge_bundle.segment->fits().size() << std::endl; + for (const auto& fit : edge_bundle.segment->fits()) { + std::cout << " Fit point: " << fit.point << " " << fit.index << " " << fit.paf.first << " " << fit.paf.second << std::endl; + } + } + } + for (auto vp = boost::vertices(*m_graph); vp.first != vp.second; ++vp.first) { + auto vd = *vp.first; + auto& v_bundle = (*m_graph)[vd]; + if (v_bundle.vertex) { + std::cout << v_bundle.vertex->fit().point << " VTX " << v_bundle.vertex->fit().index << " " << v_bundle.vertex->fit().paf.first << " " << v_bundle.vertex->fit().paf.second << std::endl; + } + } + + + dQ_dx_multi_fit(end_point_limit, flag_dQ_dx_fit_reg); + } } @@ -6899,6 +7575,7 @@ void TrackFitting::do_single_tracking(std::shared_ptr segment, bool fit.pw = pw[i]; fit.pt = pt[i]; fit.paf = paf[i]; + // std::cout <<"test " << fit.paf.first << " " << fit.paf.second << " " << paf[i].first << " " << paf[i].second << std::endl; fit.reduced_chi2 = reduced_chi2[i]; // Set trajectory information From 49421f1375c70d4605c6d8e542aec96046d9197a Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Wed, 5 Nov 2025 20:33:12 -0800 Subject: [PATCH 019/111] fix another bug --- clus/src/TrackFitting.cxx | 582 ++++++++++++++------------------------ 1 file changed, 220 insertions(+), 362 deletions(-) diff --git a/clus/src/TrackFitting.cxx b/clus/src/TrackFitting.cxx index bbf9aa57..56b88469 100644 --- a/clus/src/TrackFitting.cxx +++ b/clus/src/TrackFitting.cxx @@ -4115,7 +4115,7 @@ void TrackFitting::trajectory_fit(std::vector 1.8*units::mm * c && area1 > 1.7 * area2) flag_replace = true; + if (area1 > m_params.area_ratio1 * c && area1 > m_params.area_ratio2 * area2) flag_replace = true; } //-1, +2 if ((!flag_replace) && i>0 && i+2 1.8*units::mm * c && area1 > 1.7 * area2) flag_replace = true; + if (area1 > m_params.area_ratio1 * c && area1 > m_params.area_ratio2 * area2) flag_replace = true; } @@ -4282,7 +4282,7 @@ std::vector TrackFitting::examine_segment_trajectory(std::share s = (a + b + c) / 2.0; double area2 = sqrt(s * (s - a) * (s - b) * (s - c)); - if (area1 > 1.8*units::mm * c && area1 > 1.7 * area2) { + if (area1 > m_params.area_ratio1 * c && area1 > m_params.area_ratio2 * area2) { flag_replace = true; } } @@ -4310,7 +4310,7 @@ std::vector TrackFitting::examine_segment_trajectory(std::share s = (a + b + c) / 2.0; double area2 = sqrt(s * (s - a) * (s - b) * (s - c)); - if (area1 > 1.8*units::mm * c && area1 > 1.7 * area2) { + if (area1 > m_params.area_ratio1 * c && area1 > m_params.area_ratio2 * area2) { flag_replace = true; } } @@ -4338,7 +4338,7 @@ std::vector TrackFitting::examine_segment_trajectory(std::share s = (a + b + c) / 2.0; double area2 = sqrt(s * (s - a) * (s - b) * (s - c)); - if (area1 > 1.8*units::mm * c && area1 > 1.7 * area2) { + if (area1 > m_params.area_ratio1 * c && area1 > m_params.area_ratio2 * area2) { flag_replace = true; } } @@ -5709,6 +5709,7 @@ void TrackFitting::dQ_dx_multi_fit(double dis_end_point_ext, bool flag_dQ_dx_fit for (const auto& [vertex, vertex_idx] : vertex_index_map) { std::vector connected_pts; + WireCell::Clus::Facade::Cluster* cluster = nullptr; // Find connected segments auto vertex_desc = vertex->get_descriptor(); if (vertex_desc != PR::Graph::null_vertex()) { @@ -5719,16 +5720,18 @@ void TrackFitting::dQ_dx_multi_fit(double dis_end_point_ext, bool flag_dQ_dx_fit auto& edge_bundle = (*m_graph)[edge_desc.first]; if (edge_bundle.segment && !edge_bundle.segment->fits().empty()) { auto& fits = edge_bundle.segment->fits(); + cluster = edge_bundle.segment->cluster(); + if (fits.size() > 1){ // std::cout << vertex->fit().point << " " << vertex->fit().index << " " << fits.front().index << " " << fits.back().index << " " << vertex->fit().paf << " " << fits.front().paf << " " << fits.back().paf << " " << (vertex->fit().paf == fits[1].paf) << " " << (vertex->fit().paf == fits[fits.size() - 2].paf) << std::endl; if (vertex->fit().index == fits.front().index) { // Start point - if (vertex->fit().paf == fits[1].paf) connected_pts.push_back(fits[1].point); + if (vertex->fit().paf == fits[1].paf) connected_pts.push_back((fits[1].point + vertex->fit().point) / 2.0); } else { // End point - if (vertex->fit().paf == fits[fits.size() - 2].paf) connected_pts.push_back(fits[fits.size() - 2].point); + if (vertex->fit().paf == fits[fits.size() - 2].paf) connected_pts.push_back((fits[fits.size() - 2].point + vertex->fit().point) / 2.0); } } } @@ -5736,14 +5739,14 @@ void TrackFitting::dQ_dx_multi_fit(double dis_end_point_ext, bool flag_dQ_dx_fit } } - // std::cout << connected_pts.size() << std::endl; - // If only one connection, extend endpoint if (connected_pts.size() == 1) { WireCell::Point curr_pos = vertex->fit().point; WireCell::Vector dir = (connected_pts[0] - curr_pos).norm(); WireCell::Point extended = curr_pos - dir * dis_end_point_ext; - connected_pts[0] = extended; + connected_pts.push_back(extended); + + std::cout << (extended - curr_pos).magnitude()/units::cm << " " << (connected_pts[0] - curr_pos).magnitude()/units::cm << std::endl; } // Calculate total dx @@ -5753,318 +5756,208 @@ void TrackFitting::dQ_dx_multi_fit(double dis_end_point_ext, bool flag_dQ_dx_fit total_dx += (pt - vertex->fit().point).magnitude(); } local_dx[vertex_idx] = total_dx; - } - - // // Process vertices (endpoints) - // for (const auto& [vertex, vertex_idx] : vertex_index_map) { - // WireCell::Point curr_pos = vertex->fit().point; - - // // Build list of connected segment points - // std::vector connected_pts; - // auto vertex_desc = vertex->get_descriptor(); - // if (vertex_desc != PR::Graph::null_vertex()) { - // auto out_edges = boost::out_edges(vertex_desc, *m_graph); - // for (auto e_it = out_edges.first; e_it != out_edges.second; ++e_it) { - // auto& edge_bundle = (*m_graph)[*e_it]; - // if (edge_bundle.segment && !edge_bundle.segment->fits().empty()) { - // auto& fits = edge_bundle.segment->fits(); - // if (fits.size() > 1) { - // WireCell::Point p = 0.5 * (curr_pos + fits[1].point); - // connected_pts.push_back(p); - // } - // } - // } - // } - - // // If only one connection, extend the endpoint - // if (connected_pts.size() == 1) { - // WireCell::Vector dir = (connected_pts[0] - curr_pos).norm(); - // WireCell::Point extended = curr_pos - dir * dis_end_point_ext; - // connected_pts.push_back(extended); - // } - - // if (connected_pts.empty()) continue; + + WireCell::Point curr_pos = vertex->fit().point; + + // Create sampling points for Gaussian integration + std::vector centers_U, centers_V, centers_W, centers_T; + std::vector sigmas_T, sigmas_U, sigmas_V, sigmas_W; + std::vector weights; - // // Build Gaussian centers and sigmas for vertex - // std::vector centers_U, centers_V, centers_W, centers_T; - // std::vector sigmas_T, sigmas_U, sigmas_V, sigmas_W; - // std::vector weights; + // Get geometry parameters + auto test_wpid = m_dv->contained_by(curr_pos); + if (test_wpid.apa() == -1 || test_wpid.face() == -1) continue; + int apa = test_wpid.apa(); + int face = test_wpid.face(); + + WirePlaneId wpid(kAllLayers, test_wpid.face(), test_wpid.apa()); + auto offset_it = wpid_offsets.find(wpid); + auto slope_it = wpid_slopes.find(wpid); + auto geom_it = wpid_geoms.find(wpid); - // for (size_t k = 0; k < connected_pts.size(); k++) { - // for (int j = 0; j < 5; j++) { - // WireCell::Point reco_pos = connected_pts[k] + (curr_pos - connected_pts[k]) * (j + 0.5) / 5.0; - - // auto test_wpid = m_dv->contained_by(reco_pos); - // if (test_wpid.apa() == -1 || test_wpid.face() == -1) continue; - - // WirePlaneId wpid(kAllLayers, test_wpid.face(), test_wpid.apa()); - // auto offset_it = wpid_offsets.find(wpid); - // auto slope_it = wpid_slopes.find(wpid); - // auto geom_it = wpid_geoms.find(wpid); - - // if (offset_it == wpid_offsets.end() || slope_it == wpid_slopes.end() || geom_it == wpid_geoms.end()) continue; - - // // Get first segment's cluster for transform - // auto out_edges = boost::out_edges(vertex_desc, *m_graph); - // std::shared_ptr cluster = nullptr; - // for (auto e_it = out_edges.first; e_it != out_edges.second; ++e_it) { - // auto& edge_bundle = (*m_graph)[*e_it]; - // if (edge_bundle.segment && edge_bundle.segment->cluster()) { - // cluster = edge_bundle.segment->cluster(); - // break; - // } - // } - // if (!cluster) continue; - - // const auto transform = m_pcts->pc_transform(cluster->get_scope_transform(cluster->get_default_scope())); - // double cluster_t0 = cluster->get_cluster_t0(); - // auto reco_pos_raw = transform->backward(reco_pos, cluster_t0, test_wpid.face(), test_wpid.apa()); - - // auto offset_t = std::get<0>(offset_it->second); - // auto offset_u = std::get<1>(offset_it->second); - // auto offset_v = std::get<2>(offset_it->second); - // auto offset_w = std::get<3>(offset_it->second); - // auto slope_x = std::get<0>(slope_it->second); - // auto slope_yu = std::get<1>(slope_it->second).first; - // auto slope_zu = std::get<1>(slope_it->second).second; - // auto slope_yv = std::get<2>(slope_it->second).first; - // auto slope_zv = std::get<2>(slope_it->second).second; - // auto slope_yw = std::get<3>(slope_it->second).first; - // auto slope_zw = std::get<3>(slope_it->second).second; - - // double central_T = offset_t + slope_x * reco_pos_raw.x(); - // double central_U = offset_u + (slope_yu * reco_pos_raw.y() + slope_zu * reco_pos_raw.z()); - // double central_V = offset_v + (slope_yv * reco_pos_raw.y() + slope_zv * reco_pos_raw.z()); - // double central_W = offset_w + (slope_yw * reco_pos_raw.y() + slope_zw * reco_pos_raw.z()); - - // double weight = (connected_pts[k] - curr_pos).magnitude(); + if (offset_it == wpid_offsets.end() || slope_it == wpid_slopes.end() || geom_it == wpid_geoms.end()) continue; + const auto transform = m_pcts->pc_transform(cluster->get_scope_transform(cluster->get_default_scope())); + double cluster_t0 = cluster->get_cluster_t0(); + + auto offset_t = std::get<0>(offset_it->second); + auto offset_u = std::get<1>(offset_it->second); + auto offset_v = std::get<2>(offset_it->second); + auto offset_w = std::get<3>(offset_it->second); + auto slope_x = std::get<0>(slope_it->second); + auto slope_yu = std::get<1>(slope_it->second).first; + auto slope_zu = std::get<1>(slope_it->second).second; + auto slope_yv = std::get<2>(slope_it->second).first; + auto slope_zv = std::get<2>(slope_it->second).second; + auto slope_yw = std::get<3>(slope_it->second).first; + auto slope_zw = std::get<3>(slope_it->second).second; + + auto time_tick_width = std::get<0>(geom_it->second); + auto pitch_u = std::get<1>(geom_it->second); + auto pitch_v = std::get<2>(geom_it->second); + auto pitch_w = std::get<3>(geom_it->second); + + // Get anode and tick information for drift time calculation + auto anode = m_grouping->get_anode(test_wpid.apa()); + auto iface = anode->faces()[test_wpid.face()]; + // double xsign = iface->dirx(); + double xorig = iface->planes()[2]->wires().front()->center().x(); // Anode plane position + // double tick_size = m_grouping->get_tick().at(test_wpid.apa()).at(test_wpid.face()); + double drift_speed = m_grouping->get_drift_speed().at(test_wpid.apa()).at(test_wpid.face()); + + auto first_blob = cluster->children()[0]; + int cur_ntime_ticks = first_blob->slice_index_max() - first_blob->slice_index_min(); + + for (size_t k = 0; k < connected_pts.size(); k++) { + for (int j = 0; j < 5; j++) { + WireCell::Point reco_pos = connected_pts[k] + (curr_pos - connected_pts[k]) * (j + 0.5) / 5.0; + + auto reco_pos_raw = transform->backward(reco_pos, cluster_t0, test_wpid.face(), test_wpid.apa()); - // auto time_tick_width = std::get<0>(geom_it->second); - // double drift_time = std::max(50.0 * units::microsecond, - // reco_pos_raw.x() / time_tick_width * 0.5 * units::microsecond); + double central_T = offset_t + slope_x * reco_pos_raw.x(); + double central_U = offset_u + (slope_yu * reco_pos_raw.y() + slope_zu * reco_pos_raw.z()); + double central_V = offset_v + (slope_yv * reco_pos_raw.y() + slope_zv * reco_pos_raw.z()); + double central_W = offset_w + (slope_yw * reco_pos_raw.y() + slope_zw * reco_pos_raw.z()); - // double diff_sigma_L = sqrt(2 * DL * drift_time); - // double diff_sigma_T = sqrt(2 * DT * drift_time); + double weight = (connected_pts[k] - curr_pos).magnitude(); - // auto pitch_u = std::get<1>(geom_it->second); - // auto pitch_v = std::get<2>(geom_it->second); - // auto pitch_w = std::get<3>(geom_it->second); + // Calculate drift time from drift distance + double drift_distance = std::abs(reco_pos.x() - xorig); + double drift_time = std::max(m_params.min_drift_time, drift_distance / drift_speed); + double diff_sigma_L = sqrt(2 * DL * drift_time); + double diff_sigma_T = sqrt(2 * DT * drift_time); - // double sigma_L = sqrt(pow(diff_sigma_L, 2) + pow(add_sigma_L, 2)) / time_tick_width; - // double sigma_T_u = sqrt(pow(diff_sigma_T, 2) + pow(ind_sigma_u_T, 2)) / pitch_u; - // double sigma_T_v = sqrt(pow(diff_sigma_T, 2) + pow(ind_sigma_v_T, 2)) / pitch_v; - // double sigma_T_w = sqrt(pow(diff_sigma_T, 2) + pow(col_sigma_w_T, 2)) / pitch_w; + double sigma_L = sqrt(pow(diff_sigma_L, 2) + pow(add_sigma_L, 2)) / time_tick_width; + double sigma_T_u = sqrt(pow(diff_sigma_T, 2) + pow(ind_sigma_u_T, 2)) / pitch_u; + double sigma_T_v = sqrt(pow(diff_sigma_T, 2) + pow(ind_sigma_v_T, 2)) / pitch_v; + double sigma_T_w = sqrt(pow(diff_sigma_T, 2) + pow(col_sigma_w_T, 2)) / pitch_w; - // centers_U.push_back(central_U); - // centers_V.push_back(central_V); - // centers_W.push_back(central_W); - // centers_T.push_back(central_T); - // weights.push_back(weight); - // sigmas_U.push_back(sigma_T_u); - // sigmas_V.push_back(sigma_T_v); - // sigmas_W.push_back(sigma_T_w); - // sigmas_T.push_back(sigma_L); - // } - // } - - // if (centers_U.empty()) continue; - - // // Fill response matrices for vertex - U plane - // int n_u = 0; - // for (const auto& [coord_key, result] : map_U_charge_2D) { - // const auto& coord_2d_set = result.second; - // for (const auto& coord_2d : coord_2d_set) { - // if (abs(coord_2d.wire - centers_U.front()) <= 10 && - // abs(coord_2d.time - centers_T.front()) <= 10) { + centers_U.push_back(central_U); + centers_V.push_back(central_V); + centers_W.push_back(central_W); + centers_T.push_back(central_T); + weights.push_back(weight); + sigmas_U.push_back(sigma_T_u); + sigmas_V.push_back(sigma_T_v); + sigmas_W.push_back(sigma_T_w); + sigmas_T.push_back(sigma_L); + } + } + + int n_u = 0; + std::set> set_UT; + for (const auto& [coord_key, result] : map_U_charge_2D) { + // const auto& measurement = result.first; + const auto& coord_2d_set = result.second; + for (const auto& coord_2d : coord_2d_set) { + if (coord_2d.plane != kUlayer || coord_2d.apa != apa || coord_2d.face != face) continue; + int wire = coord_2d.wire; + int time = coord_2d.time; + set_UT.insert(std::make_pair(wire, time)); + + if (abs(wire - centers_U.front()) <= m_params.search_range && abs(time - centers_T.front()) <= m_params.search_range * cur_ntime_ticks) { - // double value = cal_gaus_integral_seg(coord_2d.time, coord_2d.wire, centers_T, sigmas_T, - // centers_U, sigmas_U, weights, 0, 4, cur_ntime_ticks); + double value = cal_gaus_integral_seg(coord_2d.time, coord_2d.wire, centers_T, sigmas_T, + centers_U, sigmas_U, weights, 0, 4, cur_ntime_ticks); - // if (result.first.flag == 0 && value > 0) reg_flag_u[vertex_idx] = 1; + if (result.first.flag == 0 && value > 0) reg_flag_u[vertex_idx] = 1; - // if (value > 0 && result.first.charge > 0 && result.first.flag != 0) { - // double total_err = sqrt(pow(result.first.charge_err, 2) + - // pow(result.first.charge * rel_uncer_ind, 2) + - // pow(add_uncer_ind, 2)); - // RU.insert(n_u, vertex_idx) = value / total_err; - // } - // } - // break; - // } - // n_u++; - // } - - // // Fill response matrices for vertex - V plane - // int n_v = 0; - // for (const auto& [coord_key, result] : map_V_charge_2D) { - // const auto& coord_2d_set = result.second; - // for (const auto& coord_2d : coord_2d_set) { - // if (abs(coord_2d.wire - centers_V.front()) <= 10 && - // abs(coord_2d.time - centers_T.front()) <= 10) { + if (value > 0 && result.first.charge > 0 && result.first.flag != 0) { + double total_err = sqrt(pow(result.first.charge_err, 2) + + pow(result.first.charge * rel_uncer_ind, 2) + + pow(add_uncer_ind, 2)); + RU.insert(n_u, vertex_idx) = value / total_err; + } + } + } + n_u++; + } + + // Fill response matrices using Gaussian integrals - V plane + int n_v = 0; + std::set> set_VT; + for (const auto& [coord_key, result] : map_V_charge_2D) { + const auto& coord_2d_set = result.second; + for (const auto& coord_2d : coord_2d_set) { + if (coord_2d.plane != kVlayer || coord_2d.apa != apa || coord_2d.face != face) continue; + int wire = coord_2d.wire; + int time = coord_2d.time; + set_VT.insert(std::make_pair(wire, time)); + if (abs(wire - centers_V.front()) <= m_params.search_range && abs(time - centers_T.front()) <= m_params.search_range * cur_ntime_ticks) { - // double value = cal_gaus_integral_seg(coord_2d.time, coord_2d.wire, centers_T, sigmas_T, - // centers_V, sigmas_V, weights, 0, 4, cur_ntime_ticks); + double value = cal_gaus_integral_seg(coord_2d.time, coord_2d.wire, centers_T, sigmas_T, + centers_V, sigmas_V, weights, 0, 4, cur_ntime_ticks); - // if (result.first.flag == 0 && value > 0) reg_flag_v[vertex_idx] = 1; + if (result.first.flag == 0 && value > 0) reg_flag_v[vertex_idx] = 1; - // if (value > 0 && result.first.charge > 0 && result.first.flag != 0) { - // double total_err = sqrt(pow(result.first.charge_err, 2) + - // pow(result.first.charge * rel_uncer_ind, 2) + - // pow(add_uncer_ind, 2)); - // RV.insert(n_v, vertex_idx) = value / total_err; - // } - // } - // break; - // } - // n_v++; - // } + if (value > 0 && result.first.charge > 0 && result.first.flag != 0) { + double total_err = sqrt(pow(result.first.charge_err, 2) + + pow(result.first.charge * rel_uncer_ind, 2) + + pow(add_uncer_ind, 2)); + RV.insert(n_v, vertex_idx) = value / total_err; + } + } + } + n_v++; + } - // // Fill response matrices for vertex - W plane - // int n_w = 0; - // for (const auto& [coord_key, result] : map_W_charge_2D) { - // const auto& coord_2d_set = result.second; - // for (const auto& coord_2d : coord_2d_set) { - // if (abs(coord_2d.wire - centers_W.front()) <= 10 && - // abs(coord_2d.time - centers_T.front()) <= 10) { - - // double value = cal_gaus_integral_seg(coord_2d.time, coord_2d.wire, centers_T, sigmas_T, - // centers_W, sigmas_W, weights, 0, 4, cur_ntime_ticks); + // Fill response matrices using Gaussian integrals - W plane + int n_w = 0; + std::set> set_WT; + for (const auto& [coord_key, result] : map_W_charge_2D) { + const auto& coord_2d_set = result.second; + for (const auto& coord_2d : coord_2d_set) { + if (coord_2d.plane != kWlayer || coord_2d.apa != apa || coord_2d.face != face) continue; + int wire = coord_2d.wire; + int time = coord_2d.time; + set_WT.insert(std::make_pair(wire, time)); + if (abs(wire - centers_W.front()) <= m_params.search_range && abs(time - centers_T.front()) <= m_params.search_range * cur_ntime_ticks) { + + double value = cal_gaus_integral_seg(time, wire, centers_T, sigmas_T, + centers_W, sigmas_W, weights, 0, 4, cur_ntime_ticks); - // if (result.first.flag == 0 && value > 0) reg_flag_w[vertex_idx] = 1; + if (result.first.flag == 0 && value > 0) reg_flag_w[vertex_idx] = 1; - // if (value > 0 && result.first.charge > 0 && result.first.flag != 0) { - // double total_err = sqrt(pow(result.first.charge_err, 2) + - // pow(result.first.charge * rel_uncer_col, 2) + - // pow(add_uncer_col, 2)); - // RW.insert(n_w, vertex_idx) = value / total_err; - // } - // } - // break; - // } - // n_w++; - // } - - // // Additional checks on dead channels for vertices - // if (reg_flag_u[vertex_idx] == 0) { - // for (size_t kk = 0; kk < centers_U.size(); kk++) { - // bool found = false; - // for (const auto& [coord_key, result] : map_U_charge_2D) { - // const auto& coord_2d_set = result.second; - // for (const auto& coord_2d : coord_2d_set) { - // if (abs(coord_2d.wire - centers_U[kk]) < 0.5 && abs(coord_2d.time - centers_T[kk]) < 0.5) { - // found = true; - // break; - // } - // } - // if (found) break; - // } - // if (!found) { - // reg_flag_u[vertex_idx] = 1; - // break; - // } - // } - // } - - // if (reg_flag_v[vertex_idx] == 0) { - // for (size_t kk = 0; kk < centers_V.size(); kk++) { - // bool found = false; - // for (const auto& [coord_key, result] : map_V_charge_2D) { - // const auto& coord_2d_set = result.second; - // for (const auto& coord_2d : coord_2d_set) { - // if (abs(coord_2d.wire - centers_V[kk]) < 0.5 && abs(coord_2d.time - centers_T[kk]) < 0.5) { - // found = true; - // break; - // } - // } - // if (found) break; - // } - // if (!found) { - // reg_flag_v[vertex_idx] = 1; - // break; - // } - // } - // } - - // if (reg_flag_w[vertex_idx] == 0) { - // for (size_t kk = 0; kk < centers_W.size(); kk++) { - // bool found = false; - // for (const auto& [coord_key, result] : map_W_charge_2D) { - // const auto& coord_2d_set = result.second; - // for (const auto& coord_2d : coord_2d_set) { - // if (abs(coord_2d.wire - centers_W[kk]) < 0.5 && abs(coord_2d.time - centers_T[kk]) < 0.5) { - // found = true; - // break; - // } - // } - // if (found) break; - // } - // if (!found) { - // reg_flag_w[vertex_idx] = 1; - // break; - // } - // } - // } - // } - - // // Copy local_dx into segment dx_vec for later use - // for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { - // auto& edge_bundle = (*m_graph)[*e_it]; - // if (!edge_bundle.segment) continue; + if (value > 0 && result.first.charge > 0 && result.first.flag != 0) { + double total_err = sqrt(pow(result.first.charge_err, 2) + + pow(result.first.charge * rel_uncer_col, 2) + + pow(add_uncer_col, 2)); + RW.insert(n_w, vertex_idx) = value / total_err; + } + } + } + n_w++; + } + + // Additional checks on dead channels for segments + if (reg_flag_u[vertex_idx] == 0) { + for (size_t kk = 0; kk < centers_U.size(); kk++) { + if (set_UT.find(std::make_pair(std::round(centers_U[kk]), std::round(centers_T[kk]/cur_ntime_ticks)*cur_ntime_ticks)) == set_UT.end()) { + reg_flag_u[vertex_idx] = 1; + break; + } + } + } - // auto segment = edge_bundle.segment; - // auto& fits = segment->fits(); - // if (fits.empty()) continue; + if (reg_flag_v[vertex_idx] == 0) { + for (size_t kk = 0; kk < centers_V.size(); kk++) { + if (set_VT.find(std::make_pair(std::round(centers_V[kk]), std::round(centers_T[kk]/cur_ntime_ticks)*cur_ntime_ticks)) == set_VT.end()) { + reg_flag_v[vertex_idx] = 1; + break; + } + } + } - // for (size_t i = 0; i < fits.size(); i++) { - // int idx = segment_point_index_map[std::make_pair(segment, i)]; - // if (idx < n_3D_pos) { - // fits[i].dx = local_dx[idx]; - // } - // } - // } - - // // Build connected_vec for regularization - // std::vector> connected_vec(n_3D_pos); - // for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { - // auto& edge_bundle = (*m_graph)[*e_it]; - // if (!edge_bundle.segment) continue; + if (reg_flag_w[vertex_idx] == 0) { + for (size_t kk = 0; kk < centers_W.size(); kk++) { + if (set_WT.find(std::make_pair(std::round(centers_W[kk]), std::round(centers_T[kk]/cur_ntime_ticks)*cur_ntime_ticks)) == set_WT.end()) { + reg_flag_w[vertex_idx] = 1; + break; + } + } + } + } - // auto segment = edge_bundle.segment; - // auto& fits = segment->fits(); - // if (fits.empty()) continue; - // for (size_t i = 1; i + 1 < fits.size(); i++) { - // int idx = segment_point_index_map[std::make_pair(segment, i)]; - // int prev_idx = segment_point_index_map[std::make_pair(segment, i-1)]; - // int next_idx = segment_point_index_map[std::make_pair(segment, i+1)]; - - // connected_vec[idx].push_back(prev_idx); - // connected_vec[idx].push_back(next_idx); - // } - // } - - // // Add vertex connections - // for (const auto& [vertex, vertex_idx] : vertex_index_map) { - // auto vertex_desc = vertex->get_descriptor(); - // if (vertex_desc != PR::Graph::null_vertex()) { - // auto out_edges = boost::out_edges(vertex_desc, *m_graph); - // for (auto e_it = out_edges.first; e_it != out_edges.second; ++e_it) { - // auto& edge_bundle = (*m_graph)[*e_it]; - // if (edge_bundle.segment && !edge_bundle.segment->fits().empty()) { - // auto& fits = edge_bundle.segment->fits(); - // // Connect vertex to second point of segment (first point is vertex itself) - // if (fits.size() > 1 && fits[0].index == vertex_idx) { - // connected_vec[vertex_idx].push_back(fits[1].index); - // } else if (fits.size() > 1 && fits.back().index == vertex_idx) { - // connected_vec[vertex_idx].push_back(fits[fits.size()-2].index); - // } - // } - // } - // } - // } - // } - // } - // Build connected_vec for regularization std::vector> connected_vec(n_3D_pos); for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { @@ -6127,10 +6020,10 @@ void TrackFitting::dQ_dx_multi_fit(double dis_end_point_ext, bool flag_dQ_dx_fit // Build regularization matrix Eigen::SparseMatrix FMatrix(n_3D_pos, n_3D_pos); - double dead_ind_weight = 0.3; - double dead_col_weight = 0.9; - double close_ind_weight = 0.25; - double close_col_weight = 0.75; + const double dead_ind_weight = m_params.dead_ind_weight; + const double dead_col_weight = m_params.dead_col_weight; + const double close_ind_weight = m_params.close_ind_weight; + const double close_col_weight = m_params.close_col_weight; for (size_t i = 0; i < n_3D_pos; i++) { if (i >= connected_vec.size()) continue; @@ -6151,18 +6044,18 @@ void TrackFitting::dQ_dx_multi_fit(double dis_end_point_ext, bool flag_dQ_dx_fit int row = i; int col = connected_vec[i][j]; - if (overlap_u[i][j] > 0.5) weight1 += close_ind_weight * pow(overlap_u[i][j] - 0.5, 2); - if (overlap_v[i][j] > 0.5) weight1 += close_ind_weight * pow(overlap_v[i][j] - 0.5, 2); - if (overlap_w[i][j] > 0.5) weight1 += close_col_weight * pow(overlap_w[i][j] - 0.5, 2); + if (overlap_u[i][j] > m_params.overlap_th) weight1 += close_ind_weight * pow(overlap_u[i][j] - 0.5, 2); + if (overlap_v[i][j] > m_params.overlap_th) weight1 += close_ind_weight * pow(overlap_v[i][j] - 0.5, 2); + if (overlap_w[i][j] > m_params.overlap_th) weight1 += close_col_weight * pow(overlap_w[i][j] - 0.5, 2); - double dx_norm = (local_dx[row] + 0.001 * units::cm) / (0.6 * units::cm); + double dx_norm = (local_dx[row] + 0.001 * units::cm) / m_params.dx_norm_length; FMatrix.coeffRef(row, row) += -weight1 * scaling / dx_norm; FMatrix.coeffRef(row, col) += weight1 * scaling / dx_norm; } } // Apply regularization strength - double lambda = 0.0008; + double lambda = m_params.lambda*8.0/5.0; // adjusted for multi-track fitting ... if (!flag_dQ_dx_fit_reg) lambda *= 0.01; FMatrix *= lambda; @@ -6187,64 +6080,28 @@ void TrackFitting::dQ_dx_multi_fit(double dis_end_point_ext, bool flag_dQ_dx_fit // Calculate reduced chi2 traj_reduced_chi2.clear(); - traj_reduced_chi2.resize(n_3D_pos, 0.0); - - std::vector traj_ndf(n_3D_pos, 0); + // std::vector traj_ndf(n_3D_pos, 0); // Calculate chi-squared contributions from U plane for (int k = 0; k < RU.outerSize(); ++k) { + double sum[3]={0,0,0}; + double sum1[3] = {0,0,0}; for (Eigen::SparseMatrix::InnerIterator it(RU, k); it; ++it) { - int row = it.row(); - int col = it.col(); - - double diff = pred_data_u_2D(row) - data_u_2D(row); - double error_sq = pow(rel_uncer_ind * data_u_2D(row), 2) + pow(add_uncer_ind, 2); - - if (error_sq > 0) { - traj_reduced_chi2[col] += pow(diff, 2) / error_sq; - traj_ndf[col]++; - } + sum[0] += pow(data_u_2D(it.row()) - pred_data_u_2D(it.row()),2) * (it.value() * pos_3D(k) )/pred_data_u_2D(it.row()); + sum1[0] += (it.value() * pos_3D(k) )/pred_data_u_2D(it.row()); } - } - - // Calculate chi-squared contributions from V plane - for (int k = 0; k < RV.outerSize(); ++k) { for (Eigen::SparseMatrix::InnerIterator it(RV, k); it; ++it) { - int row = it.row(); - int col = it.col(); - - double diff = pred_data_v_2D(row) - data_v_2D(row); - double error_sq = pow(rel_uncer_ind * data_v_2D(row), 2) + pow(add_uncer_ind, 2); - - if (error_sq > 0) { - traj_reduced_chi2[col] += pow(diff, 2) / error_sq; - traj_ndf[col]++; - } + sum[1] += pow(data_v_2D(it.row()) - pred_data_v_2D(it.row()),2) * (it.value() * pos_3D(k))/pred_data_v_2D(it.row()); + sum1[1] += (it.value() * pos_3D(k))/pred_data_v_2D(it.row()); } - } - - // Calculate chi-squared contributions from W plane - for (int k = 0; k < RW.outerSize(); ++k) { for (Eigen::SparseMatrix::InnerIterator it(RW, k); it; ++it) { - int row = it.row(); - int col = it.col(); - - double diff = pred_data_w_2D(row) - data_w_2D(row); - double error_sq = pow(rel_uncer_col * data_w_2D(row), 2) + pow(add_uncer_col, 2); - - if (error_sq > 0) { - traj_reduced_chi2[col] += pow(diff, 2) / error_sq; - traj_ndf[col]++; - } + sum[2] += pow(data_w_2D(it.row()) - pred_data_w_2D(it.row()),2) * (it.value() * pos_3D(k))/pred_data_w_2D(it.row()); + sum1[2] += (it.value()*pos_3D(k))/pred_data_w_2D(it.row()); } + traj_reduced_chi2.push_back(sqrt((sum[0] + sum[1] + sum[2]/4.)/(sum1[0]+sum1[1]+sum1[2]))); } - // Normalize chi2 by degrees of freedom - for (size_t i = 0; i < n_3D_pos; i++) { - if (traj_ndf[i] > 0) { - traj_reduced_chi2[i] /= traj_ndf[i]; - } - } + // Update vertex and segment fit results for (const auto& [vertex, vertex_idx] : vertex_index_map) { @@ -7234,12 +7091,15 @@ void TrackFitting::do_multi_tracking(bool flag_dQ_dx_fit_reg, bool flag_dQ_dx_fi } form_map_graph(flag_exclusion, m_params.end_point_factor, m_params.mid_point_factor, m_params.nlevel, m_params.time_tick_cut, m_params.charge_cut); + + dQ_dx_multi_fit(end_point_limit, flag_dQ_dx_fit_reg); + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { auto& edge_bundle = (*m_graph)[*e_it]; if (edge_bundle.segment) { std::cout << "Segment fits size: " << edge_bundle.segment->fits().size() << std::endl; for (const auto& fit : edge_bundle.segment->fits()) { - std::cout << " Fit point: " << fit.point << " " << fit.index << " " << fit.paf.first << " " << fit.paf.second << std::endl; + std::cout << " Fit point: " << fit.point << " " << fit.index << " " << fit.dQ << " " << fit.dx/units::cm << " " << fit.reduced_chi2 << std::endl; } } } @@ -7247,12 +7107,10 @@ void TrackFitting::do_multi_tracking(bool flag_dQ_dx_fit_reg, bool flag_dQ_dx_fi auto vd = *vp.first; auto& v_bundle = (*m_graph)[vd]; if (v_bundle.vertex) { - std::cout << v_bundle.vertex->fit().point << " VTX " << v_bundle.vertex->fit().index << " " << v_bundle.vertex->fit().paf.first << " " << v_bundle.vertex->fit().paf.second << std::endl; + std::cout << v_bundle.vertex->fit().point << " VTX " << v_bundle.vertex->fit().index << " " << v_bundle.vertex->fit().dQ << " " << v_bundle.vertex->fit().dx/units::cm << " " << v_bundle.vertex->fit().reduced_chi2 << std::endl; } } - - dQ_dx_multi_fit(end_point_limit, flag_dQ_dx_fit_reg); } } From 1e430e0dedb4466c625a16e9f34043b0cf1ea15e Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Thu, 6 Nov 2025 11:33:30 -0800 Subject: [PATCH 020/111] fix bug validation --- clus/src/PRSegmentFunctions.cxx | 14 +- clus/src/TaggerCheckSTM.cxx | 291 +++++++++++++------------- clus/src/TrackFitting.cxx | 349 ++++++++++++++++++++++++++++---- 3 files changed, 459 insertions(+), 195 deletions(-) diff --git a/clus/src/PRSegmentFunctions.cxx b/clus/src/PRSegmentFunctions.cxx index d130ed67..1f6489d4 100644 --- a/clus/src/PRSegmentFunctions.cxx +++ b/clus/src/PRSegmentFunctions.cxx @@ -411,9 +411,9 @@ namespace WireCell::Clus::PR { // reject if test point is at begin or end of fits. - if (itfits == fits.begin() || itfits+1 == fits.end()) { - return std::make_tuple(false, std::pair(), VertexPtr()); - } + // if (itfits == fits.begin() || itfits+1 == fits.end()) { + // return std::make_tuple(false, std::pair(), VertexPtr()); + // } const auto& wcpts = seg->wcpts(); auto itwcpts = closest_point(wcpts, point, owp_to_point); @@ -453,9 +453,11 @@ namespace WireCell::Clus::PR { seg2->cluster(seg->cluster()); // Split fits - break point included in both - seg1->fits(std::vector(fits.begin(), itfits+1)); - seg2->fits(std::vector(itfits, fits.end())); - vtx->fit(*itfits); + if (fits.size()>0){ + seg1->fits(std::vector(fits.begin(), itfits+1)); + seg2->fits(std::vector(itfits, fits.end())); + vtx->fit(*itfits); + } // Copy segment properties from original to both new segments (matching WCPPID) seg1->dir_weak(seg->dir_weak()); diff --git a/clus/src/TaggerCheckSTM.cxx b/clus/src/TaggerCheckSTM.cxx index 76df6956..dba13471 100644 --- a/clus/src/TaggerCheckSTM.cxx +++ b/clus/src/TaggerCheckSTM.cxx @@ -162,137 +162,7 @@ class TaggerCheckSTM : public IConfigurable, public Clus::IEnsembleVisitor, priv vtx2->wcpt().point = last_wcp; WireCell::Clus::PR::add_segment(*pr_graph, segment, vtx1, vtx2); - // geo_point_t test_p(10,10,10); - // const auto& fit_seg_dpc = segment->dpcloud("main"); - // auto closest_result = fit_seg_dpc->kd3d().knn(1, test_p); - // double closest_3d_distance = sqrt(closest_result[0].second); - // auto closest_2d_u = fit_seg_dpc->get_closest_2d_point_info(test_p, 0, 0, 0); - // auto closest_2d_v = fit_seg_dpc->get_closest_2d_point_info(test_p, 1, 0, 0); - // auto closest_2d_w = fit_seg_dpc->get_closest_2d_point_info(test_p, 2, 0, 0); - // std::cout << closest_3d_distance << " " << std::get<0>(closest_2d_u) << " " << std::get<0>(closest_2d_v) << " " << std::get<0>(closest_2d_w) << std::endl; - // std::cout << std::get<2>(closest_2d_u) << " " << std::get<2>(closest_2d_v) << " " << std::get<2>(closest_2d_w) << std::endl; - - m_track_fitter.add_segment(segment); - m_track_fitter.do_single_tracking(segment, true, true, false, true); - // Extract fit results from the segment - const auto& fits = segment->fits(); - std::vector vec_dQ, vec_dx; - // Print position, dQ, and dx for each fit point - std::cout << "Fit results for " << fits.size() << " points:" << std::endl; - for (size_t i = 0; i < fits.size(); ++i) { - const auto& fit = fits[i]; - // std::cout << " Point " << i << ": position=(" - // << fit.point.x()/units::cm << ", " << fit.point.y()/units::cm << ", " << fit.point.z()/units::cm - // << "), dQ=" << fit.dQ << ", dx=" << fit.dx/units::cm << std::endl; - vec_dQ.push_back(fit.dQ); - vec_dx.push_back(fit.dx); - } - // std::cout << std::endl; - const auto& wcpts = segment->wcpts(); - std::cout << "Segment WCPoints (" << wcpts.size() << "):" << std::endl; - for (size_t i = 0; i < wcpts.size(); ++i) { - const auto& wcp = wcpts[i]; - std::cout << " [" << i << "]: (" - << wcp.point.x()/units::cm << ", " - << wcp.point.y()/units::cm << ", " - << wcp.point.z()/units::cm << ") cm" << std::endl; - } - - - // test point cloud fit - create_segment_fit_point_cloud(segment, m_dv, "fit"); - - // Now access it directly from the segment - auto fit_dpcloud = segment->dpcloud("fit"); // Get the DynamicPointCloud - - if (fit_dpcloud) { - const auto& points = fit_dpcloud->get_points(); - - std::cout << "Fit point cloud has " << points.size() << " points:" << std::endl; - // for (size_t i = 0; i < points.size(); ++i) { - // std::cout << " Point " << i << ": (" - // << points[i].x/units::cm << ", " - // << points[i].y/units::cm << ", " - // << points[i].z/units::cm << ") cm" << std::endl; - // } - - // // You can also verify against the segment's fit data - // const auto& fits = segment->fits(); - // std::cout << "\nVerification:" << std::endl; - // std::cout << " Point cloud size: " << points.size() << std::endl; - // std::cout << " Segment fits size: " << fits.size() << std::endl; - - // if (points.size() == fits.size()) { - // std::cout << " ✓ Sizes match!" << std::endl; - // } - } else { - std::cout << "Fit point cloud not found on segment!" << std::endl; - } - - std::cout << "Direct Length: " << segment_track_direct_length(segment) / units::cm << " cm; " << segment_track_direct_length(segment, 0, 10) / units::cm << " cm" << " " << segment_track_direct_length(segment, -1, -1, WireCell::Vector(1,0,0)) / units::cm << " cm" << std::endl; - - std::cout << "Segment Length: " << segment_track_length(segment) / units::cm << " cm; " << segment_track_length(segment,0, 0, 10) / units::cm << " cm" << " " << segment_track_length(segment,0, -1, -1, WireCell::Vector(1,0,0)) / units::cm << " cm" << std::endl; - std::cout << "Max deviation: " << segment_track_max_deviation(segment) / units::cm << " cm; " << segment_track_max_deviation(segment, 0, 10) / units::cm << " cm" << std::endl; - std::cout << "dQ_dx: " << segment_rms_dQ_dx(segment) << " " << segment_median_dQ_dx(segment) << " " << segment_median_dQ_dx(segment,0,10) << std::endl; - - auto kink_results = segment_search_kink(segment, first_wcp, "fit"); - std::cout <<"Kink search: " << std::get<0>(kink_results) << " " << std::get<1>(kink_results) << " " << std::get<2>(kink_results) << " " << std::get<3>(kink_results) <dirsign() << " " << segment_cal_dir_3vector(segment) << " " << segment_cal_dir_3vector(segment, last_wcp, 10*units::cm) << " " << segment_cal_dir_3vector(segment, -1, 10, 1) << std::endl; - - // vec_dQ.clear(); - // vec_dx.clear(); - - // vec_dQ.push_back(35750.7); vec_dx.push_back(0.591606 * units::cm); - // vec_dQ.push_back(32381.5); vec_dx.push_back(0.532785 * units::cm); - // vec_dQ.push_back(30075.9); vec_dx.push_back(0.482393 * units::cm); - // vec_dQ.push_back(32805.1); vec_dx.push_back(0.49908 * units::cm); - // vec_dQ.push_back(46702.9); vec_dx.push_back(0.664835 * units::cm); - // vec_dQ.push_back(58132.3); vec_dx.push_back(0.779598 * units::cm); - // vec_dQ.push_back(58407.1); vec_dx.push_back(0.759001 * units::cm); - // vec_dQ.push_back(55774.6); vec_dx.push_back(0.767953 * units::cm); - // vec_dQ.push_back(51256.7); vec_dx.push_back(0.746227 * units::cm); - // vec_dQ.push_back(42653.3); vec_dx.push_back(0.607304 * units::cm); - // vec_dQ.push_back(47765.7); vec_dx.push_back(0.644757 * units::cm); - // vec_dQ.push_back(55210.0); vec_dx.push_back(0.726355 * units::cm); - // vec_dQ.push_back(44971.7); vec_dx.push_back(0.624519 * units::cm); - // vec_dQ.push_back(35688.0); vec_dx.push_back(0.541333 * units::cm); - // vec_dQ.push_back(37316.4); vec_dx.push_back(0.613396 * units::cm); - // vec_dQ.push_back(37136.7); vec_dx.push_back(0.643779 * units::cm); - // vec_dQ.push_back(33273.7); vec_dx.push_back(0.544746 * units::cm); - // vec_dQ.push_back(32636.5); vec_dx.push_back(0.546531 * units::cm); - // vec_dQ.push_back(35736.8); vec_dx.push_back(0.634489 * units::cm); - // vec_dQ.push_back(35515.5); vec_dx.push_back(0.620087 * units::cm); - // vec_dQ.push_back(36371.2); vec_dx.push_back(0.657168 * units::cm); - // vec_dQ.push_back(37250.7); vec_dx.push_back(0.78021 * units::cm); - // vec_dQ.push_back(29661.0); vec_dx.push_back(0.648785 * units::cm); - // vec_dQ.push_back(27046.6); vec_dx.push_back(0.578585 * units::cm); - // vec_dQ.push_back(28468.3); vec_dx.push_back(0.611002 * units::cm); - // vec_dQ.push_back(33398.8); vec_dx.push_back(0.685772 * units::cm); - // vec_dQ.push_back(42891.4); vec_dx.push_back(0.714633 * units::cm); - // vec_dQ.push_back(44924.0); vec_dx.push_back(0.628143 * units::cm); - - std::cout <<"Kine dQ_dx: " << segment_cal_kine_dQdx(segment, m_recomb_model) << " " << WireCell::Clus::PR::cal_kine_dQdx(vec_dQ, vec_dx, m_recomb_model) << " " << WireCell::Clus::PR::cal_kine_range(segment_track_length(segment), 13, particle_data()) << std::endl; - - std::vector L, dQ_dx; - for (size_t i = 0; i < vec_dQ.size(); ++i) { - if (i==0){ - L.push_back(0.); - }else{ - L.push_back(L.back() + vec_dx.at(i)); - } - dQ_dx.push_back(vec_dQ.at(i)/vec_dx.at(i)); // convert to per cm - } - auto pid_results = WireCell::Clus::PR::do_track_comp(L, dQ_dx, 35*units::cm, 0*units::cm, particle_data()); - std::cout << "Particle ID results: " << pid_results.at(0) << " " << pid_results.at(1) << " " << pid_results.at(2) << " " << pid_results.at(3) << std::endl; - auto results = segment_do_track_pid(segment, L, dQ_dx, particle_data()); - std::cout << std::get<0>(results) << " " << std::get<1>(results) << " " << std::get<2>(results) << " " << std::get<3>(results) << std::endl; - std::cout << "4-momentum: " << segment_cal_4mom(segment, 22, particle_data(), m_recomb_model) << " " << segment->dirsign() << std::endl; - - segment_determine_dir_track(segment, 1, 1, particle_data(), m_recomb_model, 43000/units::cm, true) ; - segment_determine_shower_direction_trajectory(segment, 1,1 , particle_data(), m_recomb_model, 43000/units::cm, true); - - // Hack: Override segment wcpts with specific data + // Hack: Override segment wcpts with specific data { std::vector new_wcpts; new_wcpts.push_back({WireCell::Point(219.539*units::cm, -86.9317*units::cm, 209.05*units::cm)}); @@ -345,6 +215,139 @@ class TaggerCheckSTM : public IConfigurable, public Clus::IEnsembleVisitor, priv segment->wcpts(new_wcpts); std::cout << "Hacked segment wcpts with " << new_wcpts.size() << " points" << std::endl; } + + + // // geo_point_t test_p(10,10,10); + // // const auto& fit_seg_dpc = segment->dpcloud("main"); + // // auto closest_result = fit_seg_dpc->kd3d().knn(1, test_p); + // // double closest_3d_distance = sqrt(closest_result[0].second); + // // auto closest_2d_u = fit_seg_dpc->get_closest_2d_point_info(test_p, 0, 0, 0); + // // auto closest_2d_v = fit_seg_dpc->get_closest_2d_point_info(test_p, 1, 0, 0); + // // auto closest_2d_w = fit_seg_dpc->get_closest_2d_point_info(test_p, 2, 0, 0); + // // std::cout << closest_3d_distance << " " << std::get<0>(closest_2d_u) << " " << std::get<0>(closest_2d_v) << " " << std::get<0>(closest_2d_w) << std::endl; + // // std::cout << std::get<2>(closest_2d_u) << " " << std::get<2>(closest_2d_v) << " " << std::get<2>(closest_2d_w) << std::endl; + + m_track_fitter.add_segment(segment); + m_track_fitter.do_single_tracking(segment, true, true, false, true); + // // Extract fit results from the segment + // const auto& fits = segment->fits(); + // std::vector vec_dQ, vec_dx; + // // Print position, dQ, and dx for each fit point + // std::cout << "Fit results for " << fits.size() << " points:" << std::endl; + // for (size_t i = 0; i < fits.size(); ++i) { + // const auto& fit = fits[i]; + // // std::cout << " Point " << i << ": position=(" + // // << fit.point.x()/units::cm << ", " << fit.point.y()/units::cm << ", " << fit.point.z()/units::cm + // // << "), dQ=" << fit.dQ << ", dx=" << fit.dx/units::cm << std::endl; + // vec_dQ.push_back(fit.dQ); + // vec_dx.push_back(fit.dx); + // } + // // std::cout << std::endl; + // const auto& wcpts = segment->wcpts(); + // std::cout << "Segment WCPoints (" << wcpts.size() << "):" << std::endl; + // for (size_t i = 0; i < wcpts.size(); ++i) { + // const auto& wcp = wcpts[i]; + // std::cout << " [" << i << "]: (" + // << wcp.point.x()/units::cm << ", " + // << wcp.point.y()/units::cm << ", " + // << wcp.point.z()/units::cm << ") cm" << std::endl; + // } + + + // // test point cloud fit + // create_segment_fit_point_cloud(segment, m_dv, "fit"); + + // // Now access it directly from the segment + // auto fit_dpcloud = segment->dpcloud("fit"); // Get the DynamicPointCloud + + // if (fit_dpcloud) { + // const auto& points = fit_dpcloud->get_points(); + + // std::cout << "Fit point cloud has " << points.size() << " points:" << std::endl; + // // for (size_t i = 0; i < points.size(); ++i) { + // // std::cout << " Point " << i << ": (" + // // << points[i].x/units::cm << ", " + // // << points[i].y/units::cm << ", " + // // << points[i].z/units::cm << ") cm" << std::endl; + // // } + + // // // You can also verify against the segment's fit data + // // const auto& fits = segment->fits(); + // // std::cout << "\nVerification:" << std::endl; + // // std::cout << " Point cloud size: " << points.size() << std::endl; + // // std::cout << " Segment fits size: " << fits.size() << std::endl; + + // // if (points.size() == fits.size()) { + // // std::cout << " ✓ Sizes match!" << std::endl; + // // } + // } else { + // std::cout << "Fit point cloud not found on segment!" << std::endl; + // } + + // std::cout << "Direct Length: " << segment_track_direct_length(segment) / units::cm << " cm; " << segment_track_direct_length(segment, 0, 10) / units::cm << " cm" << " " << segment_track_direct_length(segment, -1, -1, WireCell::Vector(1,0,0)) / units::cm << " cm" << std::endl; + + // std::cout << "Segment Length: " << segment_track_length(segment) / units::cm << " cm; " << segment_track_length(segment,0, 0, 10) / units::cm << " cm" << " " << segment_track_length(segment,0, -1, -1, WireCell::Vector(1,0,0)) / units::cm << " cm" << std::endl; + // std::cout << "Max deviation: " << segment_track_max_deviation(segment) / units::cm << " cm; " << segment_track_max_deviation(segment, 0, 10) / units::cm << " cm" << std::endl; + // std::cout << "dQ_dx: " << segment_rms_dQ_dx(segment) << " " << segment_median_dQ_dx(segment) << " " << segment_median_dQ_dx(segment,0,10) << std::endl; + + // auto kink_results = segment_search_kink(segment, first_wcp, "fit"); + // std::cout <<"Kink search: " << std::get<0>(kink_results) << " " << std::get<1>(kink_results) << " " << std::get<2>(kink_results) << " " << std::get<3>(kink_results) <dirsign() << " " << segment_cal_dir_3vector(segment) << " " << segment_cal_dir_3vector(segment, last_wcp, 10*units::cm) << " " << segment_cal_dir_3vector(segment, -1, 10, 1) << std::endl; + + // // vec_dQ.clear(); + // // vec_dx.clear(); + + // // vec_dQ.push_back(35750.7); vec_dx.push_back(0.591606 * units::cm); + // // vec_dQ.push_back(32381.5); vec_dx.push_back(0.532785 * units::cm); + // // vec_dQ.push_back(30075.9); vec_dx.push_back(0.482393 * units::cm); + // // vec_dQ.push_back(32805.1); vec_dx.push_back(0.49908 * units::cm); + // // vec_dQ.push_back(46702.9); vec_dx.push_back(0.664835 * units::cm); + // // vec_dQ.push_back(58132.3); vec_dx.push_back(0.779598 * units::cm); + // // vec_dQ.push_back(58407.1); vec_dx.push_back(0.759001 * units::cm); + // // vec_dQ.push_back(55774.6); vec_dx.push_back(0.767953 * units::cm); + // // vec_dQ.push_back(51256.7); vec_dx.push_back(0.746227 * units::cm); + // // vec_dQ.push_back(42653.3); vec_dx.push_back(0.607304 * units::cm); + // // vec_dQ.push_back(47765.7); vec_dx.push_back(0.644757 * units::cm); + // // vec_dQ.push_back(55210.0); vec_dx.push_back(0.726355 * units::cm); + // // vec_dQ.push_back(44971.7); vec_dx.push_back(0.624519 * units::cm); + // // vec_dQ.push_back(35688.0); vec_dx.push_back(0.541333 * units::cm); + // // vec_dQ.push_back(37316.4); vec_dx.push_back(0.613396 * units::cm); + // // vec_dQ.push_back(37136.7); vec_dx.push_back(0.643779 * units::cm); + // // vec_dQ.push_back(33273.7); vec_dx.push_back(0.544746 * units::cm); + // // vec_dQ.push_back(32636.5); vec_dx.push_back(0.546531 * units::cm); + // // vec_dQ.push_back(35736.8); vec_dx.push_back(0.634489 * units::cm); + // // vec_dQ.push_back(35515.5); vec_dx.push_back(0.620087 * units::cm); + // // vec_dQ.push_back(36371.2); vec_dx.push_back(0.657168 * units::cm); + // // vec_dQ.push_back(37250.7); vec_dx.push_back(0.78021 * units::cm); + // // vec_dQ.push_back(29661.0); vec_dx.push_back(0.648785 * units::cm); + // // vec_dQ.push_back(27046.6); vec_dx.push_back(0.578585 * units::cm); + // // vec_dQ.push_back(28468.3); vec_dx.push_back(0.611002 * units::cm); + // // vec_dQ.push_back(33398.8); vec_dx.push_back(0.685772 * units::cm); + // // vec_dQ.push_back(42891.4); vec_dx.push_back(0.714633 * units::cm); + // // vec_dQ.push_back(44924.0); vec_dx.push_back(0.628143 * units::cm); + + // std::cout <<"Kine dQ_dx: " << segment_cal_kine_dQdx(segment, m_recomb_model) << " " << WireCell::Clus::PR::cal_kine_dQdx(vec_dQ, vec_dx, m_recomb_model) << " " << WireCell::Clus::PR::cal_kine_range(segment_track_length(segment), 13, particle_data()) << std::endl; + + // std::vector L, dQ_dx; + // for (size_t i = 0; i < vec_dQ.size(); ++i) { + // if (i==0){ + // L.push_back(0.); + // }else{ + // L.push_back(L.back() + vec_dx.at(i)); + // } + // dQ_dx.push_back(vec_dQ.at(i)/vec_dx.at(i)); // convert to per cm + // } + // auto pid_results = WireCell::Clus::PR::do_track_comp(L, dQ_dx, 35*units::cm, 0*units::cm, particle_data()); + // std::cout << "Particle ID results: " << pid_results.at(0) << " " << pid_results.at(1) << " " << pid_results.at(2) << " " << pid_results.at(3) << std::endl; + // auto results = segment_do_track_pid(segment, L, dQ_dx, particle_data()); + // std::cout << std::get<0>(results) << " " << std::get<1>(results) << " " << std::get<2>(results) << " " << std::get<3>(results) << std::endl; + // std::cout << "4-momentum: " << segment_cal_4mom(segment, 22, particle_data(), m_recomb_model) << " " << segment->dirsign() << std::endl; + + // segment_determine_dir_track(segment, 1, 1, particle_data(), m_recomb_model, 43000/units::cm, true) ; + // segment_determine_shower_direction_trajectory(segment, 1,1 , particle_data(), m_recomb_model, 43000/units::cm, true); + + // // hack ... // std::set> segs; @@ -366,21 +369,21 @@ class TaggerCheckSTM : public IConfigurable, public Clus::IEnsembleVisitor, priv std::cout << " Segment 2 fits size: " << seg2->fits().size() << std::endl; std::cout << " Break vertex position: " << vtx_break->fit().point << " " << vtx_break->wcpt().point << std::endl; - std::set> segs; - segs.insert(seg1); - segs.insert(seg2); - clustering_points_segments(segs, m_dv, "associate_points"); + // std::set> segs; + // segs.insert(seg1); + // segs.insert(seg2); + // clustering_points_segments(segs, m_dv, "associate_points"); - auto associate_dpcloud_seg1 = seg1->dpcloud("associate_points"); // Get the DynamicPointCloud - auto associate_dpcloud_seg2 = seg2->dpcloud("associate_points"); // Get the DynamicPointCloud + // auto associate_dpcloud_seg1 = seg1->dpcloud("associate_points"); // Get the DynamicPointCloud + // auto associate_dpcloud_seg2 = seg2->dpcloud("associate_points"); // Get the DynamicPointCloud - std::cout << "Fit point cloud has " << associate_dpcloud_seg1->get_points().size() << " " << associate_dpcloud_seg2->get_points().size() << " points: " << seg1->cluster()->npoints() << std::endl; - // if (associate_dpcloud) { - // const auto& points = associate_dpcloud->get_points(); - // std::cout << "Fit point cloud has " << points.size() << " points: " << segment->cluster()->npoints() << std::endl; - // } - std::cout << segment_is_shower_topology(seg1) << " " << segment_determine_shower_direction(seg1, particle_data(), m_recomb_model) << std::endl; - std::cout << segment_is_shower_topology(seg2) << " " << segment_determine_shower_direction(seg2, particle_data(), m_recomb_model) << std::endl; + // std::cout << "Fit point cloud has " << associate_dpcloud_seg1->get_points().size() << " " << associate_dpcloud_seg2->get_points().size() << " points: " << seg1->cluster()->npoints() << std::endl; + // // if (associate_dpcloud) { + // // const auto& points = associate_dpcloud->get_points(); + // // std::cout << "Fit point cloud has " << points.size() << " points: " << segment->cluster()->npoints() << std::endl; + // // } + // std::cout << segment_is_shower_topology(seg1) << " " << segment_determine_shower_direction(seg1, particle_data(), m_recomb_model) << std::endl; + // std::cout << segment_is_shower_topology(seg2) << " " << segment_determine_shower_direction(seg2, particle_data(), m_recomb_model) << std::endl; } diff --git a/clus/src/TrackFitting.cxx b/clus/src/TrackFitting.cxx index 56b88469..e5dc2e59 100644 --- a/clus/src/TrackFitting.cxx +++ b/clus/src/TrackFitting.cxx @@ -1710,6 +1710,9 @@ void TrackFitting::organize_ps_path(std::shared_ptr segment, std::v int cur_time_slice = cluster->blob_with_point(closest_point_index)->slice_index_min(); int cur_ntime_ticks = cluster->blob_with_point(closest_point_index)->slice_index_max() - cur_time_slice; + + // std::cout << "Closest " << closest_point << " " << p << " " << temp_dis/units::cm << std::endl; + if (temp_dis < dis_cut){ // auto p_raw = transform->backward(p, cluster_t0, apa, face); @@ -2086,6 +2089,11 @@ void TrackFitting::organize_ps_path(std::shared_ptr segment, std::v min_w_dis = 0; } + // min_u_dis -=1; if (min_u_dis<0) min_u_dis=0; + // min_v_dis -=1; if (min_v_dis<0) min_v_dis=0; + // min_w_dis -=1; if (min_w_dis<0) min_w_dis=0; + + // Use the dedicated calculate_ranges_simplified function (replaces original range calculation) float range_u, range_v, range_w; WireCell::Clus::TrackFittingUtil::calculate_ranges_simplified( @@ -2095,7 +2103,7 @@ void TrackFitting::organize_ps_path(std::shared_ptr segment, std::v pitch_u, pitch_v, pitch_w, range_u, range_v, range_w); - // std::cout << min_u_dis << " " << min_v_dis << " " << min_w_dis << " " << range_u << " " << range_v << " " << range_w << std::endl; + // std::cout << vertex_time_slice << " " << min_u_dis << " " << min_v_dis << " " << min_w_dis << " " << range_u << " " << range_v << " " << range_w << std::endl; // If all ranges are positive, add wire indices to associations if (range_u > 0 && range_v > 0 && range_w > 0) { @@ -2804,11 +2812,17 @@ void TrackFitting::form_map_graph(bool flag_exclusion, double end_point_factor, 4.0/3.0 * mid_point_factor * units::cm); } + // std::cout << i << " " << fits[i].point << " " << fits[i].index << " " << end_point_factor << " " << dis_cut << std::endl; + + // Not the first and last point - process middle points if (i != 0 && i + 1 != fits.size()) { TrackFitting::PlaneData temp_2dut, temp_2dvt, temp_2dwt; form_point_association(segment, fits[i].point, temp_2dut, temp_2dvt, temp_2dwt, dis_cut, nlevel, time_tick_cut); + // std::cout << i << " " << fits[i].point << " " << temp_2dut.quantity << " " << temp_2dvt.quantity << " " << temp_2dwt.quantity << " " << temp_2dut.associated_2d_points.size() << " " << temp_2dvt.associated_2d_points.size() << " " << temp_2dwt.associated_2d_points.size() << " " << dis_cut/units::cm << " " << nlevel << " " << time_tick_cut<< std::endl; + + if (flag_exclusion) { update_association(segment, temp_2dut, temp_2dvt, temp_2dwt); } @@ -2817,6 +2831,7 @@ void TrackFitting::form_map_graph(bool flag_exclusion, double end_point_factor, bool is_end_point = (i == 1 || i + 2 == fits.size()); examine_point_association(segment, fits[i].point, temp_2dut, temp_2dvt, temp_2dwt, is_end_point, charge_cut); + if (temp_2dut.quantity + temp_2dvt.quantity + temp_2dwt.quantity > 0) { // Store in mapping structures m_3d_to_2d[count].set_plane_data(WirePlaneLayer_t::kUlayer, temp_2dut); @@ -2897,8 +2912,15 @@ void TrackFitting::form_map_graph(bool flag_exclusion, double end_point_factor, if (dummy_segment) { form_point_association(dummy_segment, pt, temp_2dut, temp_2dvt, temp_2dwt, dis_cut, nlevel, time_tick_cut); + + // std::cout << "V " << vertex->fit().point << " " << temp_2dut.quantity << " " << temp_2dvt.quantity << " " << temp_2dwt.quantity << " " << temp_2dut.associated_2d_points.size() << " " << temp_2dvt.associated_2d_points.size() << " " << temp_2dwt.associated_2d_points.size() << " " << dis_cut/units::cm << " " << nlevel << " " << time_tick_cut << std::endl; + + examine_point_association(dummy_segment, pt, temp_2dut, temp_2dvt, temp_2dwt, true, charge_cut); + + + // Store vertex associations m_3d_to_2d[vertex_count].set_plane_data(WirePlaneLayer_t::kUlayer, temp_2dut); m_3d_to_2d[vertex_count].set_plane_data(WirePlaneLayer_t::kVlayer, temp_2dvt); @@ -2965,7 +2987,7 @@ void TrackFitting::form_map(std::vector centers_U, centers_V, centers_W, centers_T; @@ -5518,6 +5543,9 @@ void TrackFitting::dQ_dx_multi_fit(double dis_end_point_ext, bool flag_dQ_dx_fit // double tick_size = m_grouping->get_tick().at(test_wpid.apa()).at(test_wpid.face()); double drift_speed = m_grouping->get_drift_speed().at(test_wpid.apa()).at(test_wpid.face()); + // std::cout << curr_pos << " " << prev_pos << " " << next_pos << std::endl; + + // Sample 5 points each from prev->curr and curr->next for (int j = 0; j < 5; j++) { // First half: prev -> curr @@ -5587,6 +5615,60 @@ void TrackFitting::dQ_dx_multi_fit(double dis_end_point_ext, bool flag_dQ_dx_fit sigmas_T.push_back(sigma_L); } + // std::cout << i << " U "; + // for (size_t idx = 0; idx < centers_U.size(); ++idx) { + // std::cout << centers_U[idx] << " "; + // } + // std::cout << std::endl; + + // std::cout << i << " V "; + // for (size_t idx = 0; idx < centers_V.size(); ++idx) { + // std::cout << centers_V[idx] << " "; + // } + // std::cout << std::endl; + + // std::cout << i << " W "; + // for (size_t idx = 0; idx < centers_W.size(); ++idx) { + // std::cout << centers_W[idx] << " "; + // } + // std::cout << std::endl; + + // std::cout << i << " T "; + // for (size_t idx = 0; idx < centers_T.size(); ++idx) { + // std::cout << centers_T[idx] << " "; + // } + // std::cout << std::endl; + + // std::cout << i << " Weights "; + // for (size_t idx = 0; idx < weights.size(); ++idx) { + // std::cout << weights[idx] << " "; + // } + // std::cout << std::endl; + + // std::cout <> set_UT; @@ -5612,6 +5694,9 @@ void TrackFitting::dQ_dx_multi_fit(double dis_end_point_ext, bool flag_dQ_dx_fit pow(result.first.charge * rel_uncer_ind, 2) + pow(add_uncer_ind, 2)); RU.insert(n_u, idx) = value / total_err; + + // std::cout << n_u << " " << i << " " << time << " " << wire << " " << i << " " << value / total_err << std::endl; + } } } @@ -5701,6 +5786,8 @@ void TrackFitting::dQ_dx_multi_fit(double dis_end_point_ext, bool flag_dQ_dx_fit } } } + // std::cout << idx << " " << reg_flag_u[idx] << " " << reg_flag_v[idx] << " " << reg_flag_w[idx] << std::endl; + } } @@ -5746,7 +5833,7 @@ void TrackFitting::dQ_dx_multi_fit(double dis_end_point_ext, bool flag_dQ_dx_fit WireCell::Point extended = curr_pos - dir * dis_end_point_ext; connected_pts.push_back(extended); - std::cout << (extended - curr_pos).magnitude()/units::cm << " " << (connected_pts[0] - curr_pos).magnitude()/units::cm << std::endl; + // std::cout << (extended - curr_pos).magnitude()/units::cm << " " << (connected_pts[0] - curr_pos).magnitude()/units::cm << std::endl; } // Calculate total dx @@ -5808,6 +5895,10 @@ void TrackFitting::dQ_dx_multi_fit(double dis_end_point_ext, bool flag_dQ_dx_fit int cur_ntime_ticks = first_blob->slice_index_max() - first_blob->slice_index_min(); for (size_t k = 0; k < connected_pts.size(); k++) { + + // std::cout << k << " " << curr_pos << " " << connected_pts[k] << std::endl; + + for (int j = 0; j < 5; j++) { WireCell::Point reco_pos = connected_pts[k] + (curr_pos - connected_pts[k]) * (j + 0.5) / 5.0; @@ -5843,6 +5934,12 @@ void TrackFitting::dQ_dx_multi_fit(double dis_end_point_ext, bool flag_dQ_dx_fit } } + // std::cout << " U "; + // for (size_t idx = 0; idx < centers_U.size(); ++idx) { + // std::cout << centers_U[idx] << " "; + // } + // std::cout << std::endl; + int n_u = 0; std::set> set_UT; for (const auto& [coord_key, result] : map_U_charge_2D) { @@ -5866,12 +5963,15 @@ void TrackFitting::dQ_dx_multi_fit(double dis_end_point_ext, bool flag_dQ_dx_fit pow(result.first.charge * rel_uncer_ind, 2) + pow(add_uncer_ind, 2)); RU.insert(n_u, vertex_idx) = value / total_err; + // std::cout << "U: " << n_u << " " << vertex_idx << " " << time << " " << wire << " " << vertex_idx << " " << value / total_err << std::endl; } } } n_u++; } + + // Fill response matrices using Gaussian integrals - V plane int n_v = 0; std::set> set_VT; @@ -5955,6 +6055,8 @@ void TrackFitting::dQ_dx_multi_fit(double dis_end_point_ext, bool flag_dQ_dx_fit } } } + + // std::cout << vertex_idx << " " << reg_flag_u[vertex_idx] << " " << reg_flag_v[vertex_idx] << " " << reg_flag_w[vertex_idx] << std::endl; } @@ -6017,6 +6119,10 @@ void TrackFitting::dQ_dx_multi_fit(double dis_end_point_ext, bool flag_dQ_dx_fit auto overlap_v = calculate_compact_matrix_multi(connected_vec, MV, RVT, n_2D_v, n_3D_pos, 3.0); auto overlap_w = calculate_compact_matrix_multi(connected_vec, MW, RWT, n_2D_w, n_3D_pos, 2.0); + // for(size_t i=0;i!=connected_vec.size();i++){ + // std::cout << i << " " << connected_vec.at(i).size() << " " << overlap_u.at(i).size() << " " << overlap_v.at(i).size() << " " << overlap_w.at(i).size() << std::endl; + // } + // Build regularization matrix Eigen::SparseMatrix FMatrix(n_3D_pos, n_3D_pos); @@ -6048,9 +6154,10 @@ void TrackFitting::dQ_dx_multi_fit(double dis_end_point_ext, bool flag_dQ_dx_fit if (overlap_v[i][j] > m_params.overlap_th) weight1 += close_ind_weight * pow(overlap_v[i][j] - 0.5, 2); if (overlap_w[i][j] > m_params.overlap_th) weight1 += close_col_weight * pow(overlap_w[i][j] - 0.5, 2); - double dx_norm = (local_dx[row] + 0.001 * units::cm) / m_params.dx_norm_length; - FMatrix.coeffRef(row, row) += -weight1 * scaling / dx_norm; - FMatrix.coeffRef(row, col) += weight1 * scaling / dx_norm; + double dx_norm_row = (local_dx[row] + 0.001 * units::cm) / m_params.dx_norm_length; + double dx_norm_col = (local_dx[col] + 0.001 * units::cm) / m_params.dx_norm_length; + FMatrix.coeffRef(row, row) += -weight1 * scaling / dx_norm_row; + FMatrix.coeffRef(row, col) += weight1 * scaling / dx_norm_col; } } @@ -6926,22 +7033,22 @@ void TrackFitting::do_multi_tracking(bool flag_dQ_dx_fit_reg, bool flag_dQ_dx_fi // std::cout << "Segment fits size: " << edge_bundle.segment->fits().size() << std::endl; // } // } - // for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { - // auto& edge_bundle = (*m_graph)[*e_it]; - // if (edge_bundle.segment) { - // std::cout << "Segment fits size: " << edge_bundle.segment->fits().size() << std::endl; - // for (const auto& fit : edge_bundle.segment->fits()) { - // std::cout << " Fit point: " << fit.point << " " << fit.index << std::endl; - // } + // for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + // auto& edge_bundle = (*m_graph)[*e_it]; + // if (edge_bundle.segment) { + // std::cout << "Segment fits size: " << edge_bundle.segment->fits().size() << std::endl; + // for (const auto& fit : edge_bundle.segment->fits()) { + // std::cout << " Fit point: " << fit.point << " " << fit.index << std::endl; // } // } - // for (auto vp = boost::vertices(*m_graph); vp.first != vp.second; ++vp.first) { - // auto vd = *vp.first; - // auto& v_bundle = (*m_graph)[vd]; - // if (v_bundle.vertex) { - // std::cout << v_bundle.vertex->fit().point << " VTX " << v_bundle.vertex->fit().index << std::endl; - // } + // } + // for (auto vp = boost::vertices(*m_graph); vp.first != vp.second; ++vp.first) { + // auto vd = *vp.first; + // auto& v_bundle = (*m_graph)[vd]; + // if (v_bundle.vertex) { + // std::cout << v_bundle.vertex->fit().point << " VTX " << v_bundle.vertex->fit().index << std::endl; // } + // } if (flag_1st_tracking){ @@ -6963,7 +7070,7 @@ void TrackFitting::do_multi_tracking(bool flag_dQ_dx_fit_reg, bool flag_dQ_dx_fi // } // } form_map_graph(flag_exclusion, m_params.end_point_factor, m_params.mid_point_factor, m_params.nlevel, m_params.time_tick_cut, m_params.charge_cut); - // for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + // for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { // auto& edge_bundle = (*m_graph)[*e_it]; // if (edge_bundle.segment) { // std::cout << "Segment fits size: " << edge_bundle.segment->fits().size() << std::endl; @@ -6980,6 +7087,88 @@ void TrackFitting::do_multi_tracking(bool flag_dQ_dx_fit_reg, bool flag_dQ_dx_fi // } // } multi_trajectory_fit(1, m_params.div_sigma); + + // std::cout << "After first Fit " << std::endl; + + + // for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + // auto& edge_bundle = (*m_graph)[*e_it]; + // if (edge_bundle.segment) { + // std::cout << "Segment fits size: " << edge_bundle.segment->fits().size() << std::endl; + // } + // } + // int count_segments = 0; + // for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + // auto& edge_bundle = (*m_graph)[*e_it]; + // if (edge_bundle.segment) { + // if (count_segments == 0){ + // std::vector override_points = { + // WireCell::Point(2192.13, -873.682, 2094.73), + // WireCell::Point(2190.05, -877.433, 2095.44), + // WireCell::Point(2187.83, -882.064, 2096.35), + // WireCell::Point(2180.81, -896.504, 2099.74), + // WireCell::Point(2177.78, -906.556, 2101.00), + // WireCell::Point(2171.11, -917.69, 2104.21), + // WireCell::Point(2166.54, -930.426, 2106.31), + // WireCell::Point(2162.36, -936.867, 2108.50), + // WireCell::Point(2158.23, -947.918, 2110.48) + // }; + // for (size_t i = 0; i < override_points.size() && i < edge_bundle.segment->fits().size(); ++i) { + // edge_bundle.segment->fits()[i].point = override_points[i]; + // } + // }else{ + // std::vector override_points = { + // WireCell::Point(2158.23, -947.918, 2110.48), + // WireCell::Point(2152.65, -958.508, 2113.46), + // WireCell::Point(2147.92, -966.199, 2115.89), + // WireCell::Point(2143.84, -977.016, 2117.63), + // WireCell::Point(2139.62, -984.948, 2119.62), + // WireCell::Point(2133.49, -997.683, 2122.44), + // WireCell::Point(2127.23, -1006.59, 2125.5), + // WireCell::Point(2123.37, -1017.77, 2127.39), + // WireCell::Point(2121.5, -1023.82, 2128.23), + // WireCell::Point(2120.98, -1028.6, 2128.63) + // }; + // for (size_t i = 0; i < override_points.size() && i < edge_bundle.segment->fits().size(); ++i) { + // edge_bundle.segment->fits()[i].point = override_points[i]; + // } + // } + + // std::cout << "Segment fits size: " << edge_bundle.segment->fits().size() << std::endl; + // for (const auto& fit : edge_bundle.segment->fits()) { + // std::cout << " Fit point: " << fit.point << " " << fit.index << std::endl; + // } + // } + // count_segments++; + // } + // for (auto vp = boost::vertices(*m_graph); vp.first != vp.second; ++vp.first) { + // auto vd = *vp.first; + // auto& v_bundle = (*m_graph)[vd]; + // if (v_bundle.vertex) { + // // Set vertex fit point to the closest of the three given points + // std::vector candidate_points = { + // WireCell::Point(2192.13, -873.682, 2094.73), + // WireCell::Point(2158.23, -947.918, 2110.48), + // WireCell::Point(2120.98, -1028.6, 2128.63) + // }; + // double min_dist = std::numeric_limits::max(); + // WireCell::Point closest_point = v_bundle.vertex->fit().point; + // for (const auto& cp : candidate_points) { + // double dist = sqrt(pow(cp.x() - v_bundle.vertex->fit().point.x(), 2) + + // pow(cp.y() - v_bundle.vertex->fit().point.y(), 2) + + // pow(cp.z() - v_bundle.vertex->fit().point.z(), 2)); + // if (dist < min_dist) { + // min_dist = dist; + // closest_point = cp; + // } + // } + // v_bundle.vertex->fit().point = closest_point; + + + // std::cout << v_bundle.vertex->fit().point << " VTX " << v_bundle.vertex->fit().index << std::endl; + // } + // } + } @@ -7030,20 +7219,88 @@ void TrackFitting::do_multi_tracking(bool flag_dQ_dx_fit_reg, bool flag_dQ_dx_fi form_map_graph(flag_exclusion, m_params.end_point_factor, m_params.mid_point_factor, m_params.nlevel, m_params.time_tick_cut, m_params.charge_cut); multi_trajectory_fit(1, m_params.div_sigma); - // for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + // std::cout << "After second Fit " << std::endl; + + // int count_segments = 0; + // for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { // auto& edge_bundle = (*m_graph)[*e_it]; // if (edge_bundle.segment) { - // std::cout << "Segment fits size: " << edge_bundle.segment->fits().size() << std::endl; - // for (const auto& fit : edge_bundle.segment->fits()) { - // std::cout << " Fit point: " << fit.point << " " << fit.index << " " << fit.paf.first << " " << fit.paf.second << std::endl; + // if (count_segments == 0){ + // std::vector override_points = { + // WireCell::Point(2191.77, -873.687, 2094.66), + // WireCell::Point(2189.93, -878.246, 2095.67), + // WireCell::Point(2188.76, -881.477, 2096.10), + // WireCell::Point(2185.86, -886.508, 2097.41), + // WireCell::Point(2183.5, -890.672, 2098.29), + // WireCell::Point(2180.25, -899.103, 2099.89), + // WireCell::Point(2179.17, -902.032, 2100.32), + // WireCell::Point(2177.74, -905.425, 2101.21), + // WireCell::Point(2174.8, -911.866, 2102.45), + // WireCell::Point(2170.12, -919.848, 2104.71), + // WireCell::Point(2167.97, -925.905, 2105.65), + // WireCell::Point(2166.54, -930.426, 2106.31), + // WireCell::Point(2162.86, -936.994, 2108.18), + // WireCell::Point(2160.29, -942.265, 2109.29), + // WireCell::Point(2158.08, -947.736, 2110.60) + // }; + // for (size_t i = 0; i < override_points.size() && i < edge_bundle.segment->fits().size(); ++i) { + // edge_bundle.segment->fits()[i].point = override_points[i]; + // } + // }else{ + // std::vector override_points = { + // WireCell::Point(2158.08, -947.736, 2110.6), + // WireCell::Point(2155.04, -952.146, 2112.02), + // WireCell::Point(2152.23, -958.438, 2113.63), + // WireCell::Point(2147.92, -966.199, 2115.89), + // WireCell::Point(2147.03, -967.753, 2116.48), + // WireCell::Point(2143.84, -977.016, 2117.63), + // WireCell::Point(2139.8, -983.235, 2119.51), + // WireCell::Point(2136.43, -990.294, 2120.93), + // WireCell::Point(2133.67, -997.807, 2122.05), + // WireCell::Point(2130.73, -1001.33, 2123.6), + // WireCell::Point(2127.06, -1006.35, 2125.66), + // WireCell::Point(2125.3, -1012.18, 2126.44), + // WireCell::Point(2121.84, -1018.37, 2128.12), + // WireCell::Point(2120.71, -1023.03, 2128.69), + // WireCell::Point(2120.71, -1026.48, 2128.67) + // }; + // for (size_t i = 0; i < override_points.size() && i < edge_bundle.segment->fits().size(); ++i) { + // edge_bundle.segment->fits()[i].point = override_points[i]; + // } // } + + // // std::cout << "Segment fits size: " << edge_bundle.segment->fits().size() << std::endl; + // // for (const auto& fit : edge_bundle.segment->fits()) { + // // std::cout << " Fit point: " << fit.point << " " << fit.index << std::endl; + // // } // } + // count_segments++; // } // for (auto vp = boost::vertices(*m_graph); vp.first != vp.second; ++vp.first) { // auto vd = *vp.first; // auto& v_bundle = (*m_graph)[vd]; // if (v_bundle.vertex) { - // std::cout << v_bundle.vertex->fit().point << " VTX " << v_bundle.vertex->fit().index << " " << v_bundle.vertex->fit().paf.first << " " << v_bundle.vertex->fit().paf.second << std::endl; + // // Set vertex fit point to the closest of the three given points + // std::vector candidate_points = { + // WireCell::Point(2191.77, -873.687, 2094.66), + // WireCell::Point(2158.08, -947.736, 2110.6), + // WireCell::Point(2120.71, -1026.48, 2128.67) + // }; + // double min_dist = std::numeric_limits::max(); + // WireCell::Point closest_point = v_bundle.vertex->fit().point; + // for (const auto& cp : candidate_points) { + // double dist = sqrt(pow(cp.x() - v_bundle.vertex->fit().point.x(), 2) + + // pow(cp.y() - v_bundle.vertex->fit().point.y(), 2) + + // pow(cp.z() - v_bundle.vertex->fit().point.z(), 2)); + // if (dist < min_dist) { + // min_dist = dist; + // closest_point = cp; + // } + // } + // v_bundle.vertex->fit().point = closest_point; + + + // // std::cout << v_bundle.vertex->fit().point << " VTX " << v_bundle.vertex->fit().index << std::endl; // } // } @@ -7051,6 +7308,7 @@ void TrackFitting::do_multi_tracking(bool flag_dQ_dx_fit_reg, bool flag_dQ_dx_fi low_dis_limit = 0.6*units::cm; organize_segments_path_3rd(low_dis_limit); + // std::cout << "After third organization " << std::endl; // for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { // auto& edge_bundle = (*m_graph)[*e_it]; // if (edge_bundle.segment) { @@ -7091,25 +7349,26 @@ void TrackFitting::do_multi_tracking(bool flag_dQ_dx_fit_reg, bool flag_dQ_dx_fi } form_map_graph(flag_exclusion, m_params.end_point_factor, m_params.mid_point_factor, m_params.nlevel, m_params.time_tick_cut, m_params.charge_cut); - + + dQ_dx_multi_fit(end_point_limit, flag_dQ_dx_fit_reg); - for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { - auto& edge_bundle = (*m_graph)[*e_it]; - if (edge_bundle.segment) { - std::cout << "Segment fits size: " << edge_bundle.segment->fits().size() << std::endl; - for (const auto& fit : edge_bundle.segment->fits()) { - std::cout << " Fit point: " << fit.point << " " << fit.index << " " << fit.dQ << " " << fit.dx/units::cm << " " << fit.reduced_chi2 << std::endl; - } - } - } - for (auto vp = boost::vertices(*m_graph); vp.first != vp.second; ++vp.first) { - auto vd = *vp.first; - auto& v_bundle = (*m_graph)[vd]; - if (v_bundle.vertex) { - std::cout << v_bundle.vertex->fit().point << " VTX " << v_bundle.vertex->fit().index << " " << v_bundle.vertex->fit().dQ << " " << v_bundle.vertex->fit().dx/units::cm << " " << v_bundle.vertex->fit().reduced_chi2 << std::endl; - } - } + // for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + // auto& edge_bundle = (*m_graph)[*e_it]; + // if (edge_bundle.segment) { + // std::cout << "Segment fits size: " << edge_bundle.segment->fits().size() << std::endl; + // for (const auto& fit : edge_bundle.segment->fits()) { + // std::cout << " Fit point: " << fit.point << " " << fit.index << " " << fit.dQ << " " << fit.dx/units::cm << " " << fit.reduced_chi2 << std::endl; + // } + // } + // } + // for (auto vp = boost::vertices(*m_graph); vp.first != vp.second; ++vp.first) { + // auto vd = *vp.first; + // auto& v_bundle = (*m_graph)[vd]; + // if (v_bundle.vertex) { + // std::cout << v_bundle.vertex->fit().point << " VTX " << v_bundle.vertex->fit().index << " " << v_bundle.vertex->fit().dQ << " " << v_bundle.vertex->fit().dx/units::cm << " " << v_bundle.vertex->fit().reduced_chi2 << std::endl; + // } + // } } } From 7b37aabf28aaafd2af763d416d721805b258cb2f Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Thu, 27 Nov 2025 07:40:54 -0800 Subject: [PATCH 021/111] add a function --- clus/inc/WireCellClus/PRGraph.h | 10 ++++++++++ clus/src/PRGraph.cxx | 26 ++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/clus/inc/WireCellClus/PRGraph.h b/clus/inc/WireCellClus/PRGraph.h index cb515f04..33334259 100644 --- a/clus/inc/WireCellClus/PRGraph.h +++ b/clus/inc/WireCellClus/PRGraph.h @@ -117,6 +117,16 @@ namespace WireCell::Clus::PR { /// one with a "wcpoint" closest to the segment's initial "wcpoint". std::pair find_endpoints(Graph& graph, SegmentPtr seg); + /// Return the other vertex connected to a segment, given one known vertex. + /// + /// Returns nullptr if: + /// - The segment edge is not in the graph + /// - The given vertex is not connected to the segment + /// + /// This is useful when you have one vertex of a segment and need to find + /// the vertex at the other end. + VertexPtr find_other_vertex(Graph& graph, SegmentPtr seg, VertexPtr vertex); + }; diff --git a/clus/src/PRGraph.cxx b/clus/src/PRGraph.cxx index 0d858f7b..3fe00ba8 100644 --- a/clus/src/PRGraph.cxx +++ b/clus/src/PRGraph.cxx @@ -91,6 +91,32 @@ namespace WireCell::Clus::PR { return std::make_pair(vtx2, vtx1); } + VertexPtr find_other_vertex(Graph& graph, SegmentPtr seg, VertexPtr vertex) + { + if (! seg->descriptor_valid()) { return nullptr; } + if (! vertex->descriptor_valid()) { return nullptr; } + + auto ed = seg->get_descriptor(); + auto vd = vertex->get_descriptor(); + + auto vd1 = boost::source(ed, graph); + auto vd2 = boost::target(ed, graph); + + auto [ed2,ingraph] = boost::edge(vd1, vd2, graph); + if (!ingraph) { return nullptr; } + + // Check if the given vertex is connected to this segment + if (vd == vd1) { + return graph[vd2].vertex; + } + else if (vd == vd2) { + return graph[vd1].vertex; + } + + // Given vertex is not connected to this segment + return nullptr; + } + } From 8bb47838c4f4a03a7b7439d61129fd98a2a860d4 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Thu, 27 Nov 2025 08:25:32 -0800 Subject: [PATCH 022/111] add a function of check_direction --- clus/src/connect_graph.cxx | 73 +++++++++++++++++++++++++++++++++++++- clus/src/connect_graphs.h | 2 ++ 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/clus/src/connect_graph.cxx b/clus/src/connect_graph.cxx index 41728513..a8167dea 100644 --- a/clus/src/connect_graph.cxx +++ b/clus/src/connect_graph.cxx @@ -513,4 +513,75 @@ bool Graphs::is_point_good(const Cluster& cluster, size_t point_index, int ncut) if (charge_w > 10) ncount++; return ncount >= ncut; -} \ No newline at end of file +} + +std::vector check_direction(const Facade::Cluster& cluster, Facade::geo_vector_t& v1, int apa, int face, double angle_cut_1, double angle_cut_2){ + // Get grouping to access wire geometry + auto grouping = cluster.grouping(); + if (!grouping) { + // Return all false if no grouping available + return std::vector(4, false); + } + + // Get wire angles from grouping for this APA and face + auto [angle_u, angle_v, angle_w] = grouping->wire_angles(apa, face); + + // Get drift direction from grouping + int drift_dirx = grouping->get_drift_dir().at(apa).at(face); + Facade::geo_vector_t drift_dir_abs(fabs(drift_dirx), 0, 0); + + // Construct wire direction vectors + // U wire: angle_u from Y axis in YZ plane + Facade::geo_vector_t U_dir(0, std::cos(angle_u), std::sin(angle_u)); + // V wire: angle_v from Y axis in YZ plane + Facade::geo_vector_t V_dir(0, std::cos(angle_v), std::sin(angle_v)); + // W wire: angle_w from Y axis in YZ plane + Facade::geo_vector_t W_dir(0, std::cos(angle_w), std::sin(angle_w)); + + // Project v1 onto YZ plane + Facade::geo_vector_t tempV1(0, v1.y(), v1.z()); + Facade::geo_vector_t tempV5; + + // Prolonged U - project onto plane perpendicular to U wire direction + double angle1 = tempV1.angle(U_dir); + tempV5 = Facade::geo_vector_t( + std::fabs(v1.x()), + std::sqrt(v1.y()*v1.y() + v1.z()*v1.z()) * std::sin(angle1), + 0 + ); + angle1 = tempV5.angle(drift_dir_abs); + + // Prolonged V - project onto plane perpendicular to V wire direction + double angle2 = tempV1.angle(V_dir); + tempV5 = Facade::geo_vector_t( + std::fabs(v1.x()), + std::sqrt(v1.y()*v1.y() + v1.z()*v1.z()) * std::sin(angle2), + 0 + ); + angle2 = tempV5.angle(drift_dir_abs); + + // Prolonged W - project onto plane perpendicular to W wire direction + double angle3 = tempV1.angle(W_dir); + tempV5 = Facade::geo_vector_t( + std::fabs(v1.x()), + std::sqrt(v1.y()*v1.y() + v1.z()*v1.z()) * std::sin(angle3), + 0 + ); + angle3 = tempV5.angle(drift_dir_abs); + + // Parallel - angle with respect to drift direction + double angle4 = v1.angle(drift_dir); + + std::vector results(4, false); + + // Check if prolonged along U wire (< 12.5 degrees) + if (angle1 < angle_cut_1 / 180.0 * M_PI) results.at(0) = true; + // Check if prolonged along V wire (< 12.5 degrees) + if (angle2 < angle_cut_1 / 180.0 * M_PI) results.at(1) = true; + // Check if prolonged along W wire (< 12.5 degrees) + if (angle3 < angle_cut_1 / 180.0 * M_PI) results.at(2) = true; + // Check if perpendicular to drift (within 10 degrees of 90 degrees) + if (std::fabs(angle4 - M_PI/2.0) < angle_cut_2 / 180.0 * M_PI) results.at(3) = true; + + return results; +} diff --git a/clus/src/connect_graphs.h b/clus/src/connect_graphs.h index e74a3faf..82581f52 100644 --- a/clus/src/connect_graphs.h +++ b/clus/src/connect_graphs.h @@ -45,6 +45,8 @@ namespace WireCell::Clus::Graphs { Weighted::Graph& graph); bool is_point_good(const Facade::Cluster& cluster, size_t point_index, int ncut = 3); + + std::vector check_direction(const Facade::Cluster& cluster, Facade::geo_vector_t& v1, int apa, int face, double angle_cut_1 = 12.5, double angle_cut_2 = 10); } #endif From 8d485e55bd66b4b37057682e63a483b3578e3bb0 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Thu, 27 Nov 2025 09:34:09 -0800 Subject: [PATCH 023/111] implement check_connectivity function --- clus/src/connect_graph.cxx | 6 +- clus/src/connect_graph_relaxed.cxx | 164 +++++++++++++++++++++++++++++ clus/src/connect_graphs.h | 3 + 3 files changed, 170 insertions(+), 3 deletions(-) diff --git a/clus/src/connect_graph.cxx b/clus/src/connect_graph.cxx index a8167dea..8d90abac 100644 --- a/clus/src/connect_graph.cxx +++ b/clus/src/connect_graph.cxx @@ -515,7 +515,7 @@ bool Graphs::is_point_good(const Cluster& cluster, size_t point_index, int ncut) return ncount >= ncut; } -std::vector check_direction(const Facade::Cluster& cluster, Facade::geo_vector_t& v1, int apa, int face, double angle_cut_1, double angle_cut_2){ +std::vector Graphs::check_direction(const Facade::Cluster& cluster, Facade::geo_vector_t& v1, int apa, int face, double angle_cut_1, double angle_cut_2){ // Get grouping to access wire geometry auto grouping = cluster.grouping(); if (!grouping) { @@ -528,7 +528,7 @@ std::vector check_direction(const Facade::Cluster& cluster, Facade::geo_ve // Get drift direction from grouping int drift_dirx = grouping->get_drift_dir().at(apa).at(face); - Facade::geo_vector_t drift_dir_abs(fabs(drift_dirx), 0, 0); + Facade::geo_vector_t drift_dir_abs(std::fabs(drift_dirx), 0, 0); // Construct wire direction vectors // U wire: angle_u from Y axis in YZ plane @@ -570,7 +570,7 @@ std::vector check_direction(const Facade::Cluster& cluster, Facade::geo_ve angle3 = tempV5.angle(drift_dir_abs); // Parallel - angle with respect to drift direction - double angle4 = v1.angle(drift_dir); + double angle4 = v1.angle(drift_dir_abs); std::vector results(4, false); diff --git a/clus/src/connect_graph_relaxed.cxx b/clus/src/connect_graph_relaxed.cxx index cebb39b4..43f4785c 100644 --- a/clus/src/connect_graph_relaxed.cxx +++ b/clus/src/connect_graph_relaxed.cxx @@ -602,3 +602,167 @@ void Graphs::connect_graph_relaxed( } + bool check_connectivity(const Facade::Cluster& cluster, IDetectorVolumes::pointer dv, IPCTransformSet::pointer pcts, std::tuple& index_index_dis, std::shared_ptr pc1, std::shared_ptr pc2, double step_size, bool flag_strong_check){ + + // Check if indices are valid + if (std::get<0>(index_index_dis) == -1 || std::get<1>(index_index_dis) == -1) return false; + + // Get points from cluster + const auto& points = cluster.points(); + + // Get the two points from the point cloud + int idx1 = std::get<0>(index_index_dis); + int idx2 = std::get<1>(index_index_dis); + + geo_point_t p1(points[0][idx1], points[1][idx1], points[2][idx1]); + geo_point_t p2(points[0][idx2], points[1][idx2], points[2][idx2]); + + // Get wire plane IDs for the two points + auto wpid_p1 = cluster.wire_plane_id(idx1); + auto wpid_p2 = cluster.wire_plane_id(idx2); + auto wpid_pc = get_wireplaneid(p1, wpid_p1, p2, wpid_p2, dv); + + int apa1 = wpid_p1.apa(); + int face1 = wpid_p1.face(); + int apa2 = wpid_p2.apa(); + int face2 = wpid_p2.face(); + int apa3 = wpid_pc.apa(); + int face3 = wpid_pc.face(); + + // Get grouping for CTPC checks + const auto* grouping = cluster.grouping(); + + // Calculate directions using VHoughTrans equivalent (vhough_transform) + // Use point clouds pc1 and pc2 for local direction calculation + geo_vector_t dir1 = cluster.vhough_transform(p1, 15*units::cm, Cluster::HoughParamSpace::theta_phi); + dir1 = dir1 * -1; + + geo_vector_t dir2 = cluster.vhough_transform(p2, 15*units::cm, Cluster::HoughParamSpace::theta_phi); + dir2 = dir2 * -1; + + geo_vector_t dir3(p1.x() - p2.x(), p1.y() - p2.y(), p1.z() - p2.z()); + + // Check directions using the check_direction function + std::vector flag_1 = check_direction(cluster, dir1, apa1, face1); + std::vector flag_2 = check_direction(cluster, dir2, apa2, face2); + + // For dir3, use either apa/face from p1 or p2 (use p1 as reference) + std::vector flag_3 = check_direction(cluster, dir3, apa3, face3); + + bool flag_prolonged_u = false; + bool flag_prolonged_v = false; + bool flag_prolonged_w = false; + bool flag_parallel = false; + + // Check if prolonged along wire directions or parallel to drift + if (flag_3.at(0) && (flag_1.at(0) || flag_2.at(0))) flag_prolonged_u = true; + if (flag_3.at(1) && (flag_1.at(1) || flag_2.at(1))) flag_prolonged_v = true; + if (flag_3.at(2) && (flag_1.at(2) || flag_2.at(2))) flag_prolonged_w = true; + if (flag_3.at(3) && (flag_1.at(3) && flag_2.at(3))) flag_parallel = true; + + // Calculate distance and number of steps + double dis = std::sqrt(std::pow(p1.x() - p2.x(), 2) + + std::pow(p1.y() - p2.y(), 2) + + std::pow(p1.z() - p2.z(), 2)); + int num_steps = std::round(dis / step_size); + + if (num_steps == 0) num_steps = 1; + + int num_bad[5] = {0, 0, 0, 0, 0}; + + double radius_cut = 0.6 * units::cm; + if (step_size < radius_cut) radius_cut = step_size; + + // Check points along the path + for (int i = 0; i != num_steps; i++) { + geo_point_t test_p( + p1.x() + (p2.x() - p1.x()) / (num_steps + 1.0) * (i + 1), + p1.y() + (p2.y() - p1.y()) / (num_steps + 1.0) * (i + 1), + p1.z() + (p2.z() - p1.z()) / (num_steps + 1.0) * (i + 1) + ); + + // Get wire plane ID for test point + auto test_wpid = get_wireplaneid(test_p, wpid_p1, wpid_p2, dv); + + if (test_wpid.apa() == -1) continue; + + // Transform point if needed + geo_point_t test_p_raw = test_p; + if (cluster.get_default_scope().hash() != cluster.get_raw_scope().hash()) { + const auto transform = pcts->pc_transform(cluster.get_scope_transform()); + double cluster_t0 = cluster.get_cluster_t0(); + test_p_raw = transform->backward(test_p, cluster_t0, test_wpid.face(), test_wpid.apa()); + } + + // Test point quality with appropriate radius + double test_radius; + if (i == 0 || i + 1 == num_steps) { + test_radius = dis / (num_steps + 1.0) * 0.98; + } else { + if (flag_strong_check) { + test_radius = dis / (num_steps + 1.0); + } else { + test_radius = radius_cut; + } + } + + // Get detailed scores for this point + std::vector scores = grouping->test_good_point(test_p_raw, test_wpid.apa(), test_wpid.face(), test_radius); + + int num_bad_details = 0; + + // Check U plane (indices 0=live, 3=dead) + if (scores.at(0) + scores.at(3) == 0) { + if (!flag_prolonged_u) num_bad[0]++; + num_bad_details++; + } + + // Check V plane (indices 1=live, 4=dead) + if (scores.at(1) + scores.at(4) == 0) { + if (!flag_prolonged_v) num_bad[1]++; + num_bad_details++; + } + + // Check W plane (collection, indices 2=live, 5=dead) + if (scores.at(2) + scores.at(5) == 0) { + if (!flag_prolonged_w) num_bad[2]++; + num_bad_details++; + } + + // Count overall bad points + if (flag_parallel) { + // Parallel case: more than one plane bad + if (num_bad_details > 1) num_bad[3]++; + } else { + // Non-parallel: any plane bad + if (num_bad_details > 0) num_bad[3]++; + } + } + + // Strong check - very strict criteria + if (flag_strong_check && ((num_bad[0] + num_bad[1] + num_bad[2]) > 0 || num_bad[3] >= 2)) { + return false; + } + + // Prolonged case - allow some bad points but not too many + if (num_bad[0] <= 2 && num_bad[1] <= 2 && num_bad[2] <= 2 && + (num_bad[0] + num_bad[1] + num_bad[2] <= 3) && + num_bad[0] < 0.1 * num_steps && + num_bad[1] < 0.1 * num_steps && + num_bad[2] < 0.1 * num_steps && + (num_bad[0] + num_bad[1] + num_bad[2]) < 0.15 * num_steps) { + + // Special case: if prolonged in all three directions, check overall quality + if (flag_prolonged_u && flag_prolonged_v && flag_prolonged_w) { + if (num_bad[3] >= 0.6 * num_steps) return false; + } + + return true; + } + // Alternative case - overall good quality + else if (num_bad[3] <= 2 && num_bad[3] < 0.1 * num_steps) { + return true; + } + + return false; + } \ No newline at end of file diff --git a/clus/src/connect_graphs.h b/clus/src/connect_graphs.h index 82581f52..872135b2 100644 --- a/clus/src/connect_graphs.h +++ b/clus/src/connect_graphs.h @@ -47,6 +47,9 @@ namespace WireCell::Clus::Graphs { bool is_point_good(const Facade::Cluster& cluster, size_t point_index, int ncut = 3); std::vector check_direction(const Facade::Cluster& cluster, Facade::geo_vector_t& v1, int apa, int face, double angle_cut_1 = 12.5, double angle_cut_2 = 10); + + bool check_connectivity(const Facade::Cluster& cluster, IDetectorVolumes::pointer dv, + IPCTransformSet::pointer pcts, std::tuple& index_index_dis, std::shared_ptr pc1, std::shared_ptr pc2, double step_size = 0.6*units::cm, bool flag_strong_check = false); } #endif From 7444f5a517619925f4e8ce29d0f46758b48432af Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Thu, 27 Nov 2025 10:54:16 -0800 Subject: [PATCH 024/111] implement the connect_graph_overclustering_projection as connect_graph_relaxed_pi --- clus/inc/WireCellClus/Facade_Util.h | 3 + clus/src/Facade_Util.cxx | 22 ++ clus/src/connect_graph_relaxed.cxx | 362 +++++++++++++++++++++++++++- clus/src/connect_graphs.h | 11 +- 4 files changed, 387 insertions(+), 11 deletions(-) diff --git a/clus/inc/WireCellClus/Facade_Util.h b/clus/inc/WireCellClus/Facade_Util.h index b898d2ab..13e4598b 100644 --- a/clus/inc/WireCellClus/Facade_Util.h +++ b/clus/inc/WireCellClus/Facade_Util.h @@ -144,6 +144,9 @@ namespace WireCell::Clus::Facade { results_type get_closest_index(const geo_point_t& p, const size_t N) const; /// @return index, geo_point_t std::pair get_closest_wcpoint(const geo_point_t& p) const; + double get_closest_dis(const geo_point_t& p) const; + + std::vector> get_closest_wcpoints_radius(const geo_point_t& p, const double radius) const; /// @param p_test1 is the point to start from /// @param dir is the direction to search along diff --git a/clus/src/Facade_Util.cxx b/clus/src/Facade_Util.cxx index 620f1ace..cf1acb66 100644 --- a/clus/src/Facade_Util.cxx +++ b/clus/src/Facade_Util.cxx @@ -155,6 +155,28 @@ std::pair Facade::Simple3DPointCloud::get_closest_wcpoint(c // std::cout << "get_closest_wcpoint: " << p << " " << ind << " " << pt << " " << knn_res[0].second << std::endl; return std::make_pair(ind, pt); } + +double Facade::Simple3DPointCloud::get_closest_dis(const geo_point_t& p) const { + const auto knn_res = kd().knn(1, p); + if (knn_res.size() != 1) { + raise("no points found"); + } + // KD-tree returns squared distance, so take sqrt for linear distance + return std::sqrt(knn_res[0].second); +} + +std::vector> Facade::Simple3DPointCloud::get_closest_wcpoints_radius(const geo_point_t& p, const double radius) const{ + std::vector> results; + // Note: radius() expects squared distance for L2 metric + const auto res = kd().radius(radius * radius, p); + for (const auto& item : res) { + const auto ind = item.first; + geo_point_t pt = {points()[0][ind], points()[1][ind], points()[2][ind]}; + results.emplace_back(ind, pt); + } + return results; +} + std::pair Facade::Simple3DPointCloud::get_closest_point_along_vec(const geo_point_t& p_test1, const geo_point_t& dir, double test_dis, double dis_step, double angle_cut, diff --git a/clus/src/connect_graph_relaxed.cxx b/clus/src/connect_graph_relaxed.cxx index 43f4785c..a76c26b6 100644 --- a/clus/src/connect_graph_relaxed.cxx +++ b/clus/src/connect_graph_relaxed.cxx @@ -602,24 +602,25 @@ void Graphs::connect_graph_relaxed( } - bool check_connectivity(const Facade::Cluster& cluster, IDetectorVolumes::pointer dv, IPCTransformSet::pointer pcts, std::tuple& index_index_dis, std::shared_ptr pc1, std::shared_ptr pc2, double step_size, bool flag_strong_check){ + bool Graphs::check_connectivity(const Facade::Cluster& cluster, IDetectorVolumes::pointer dv, IPCTransformSet::pointer pcts, std::tuple& index_index_dis, std::shared_ptr pc1, std::vector pc1_global_index, std::shared_ptr pc2, std::vector pc2_global_index, + double step_size, bool flag_strong_check){ // Check if indices are valid if (std::get<0>(index_index_dis) == -1 || std::get<1>(index_index_dis) == -1) return false; // Get points from cluster - const auto& points = cluster.points(); + // const auto& points = cluster.points(); // Get the two points from the point cloud int idx1 = std::get<0>(index_index_dis); int idx2 = std::get<1>(index_index_dis); - geo_point_t p1(points[0][idx1], points[1][idx1], points[2][idx1]); - geo_point_t p2(points[0][idx2], points[1][idx2], points[2][idx2]); + geo_point_t p1= pc1->point(idx1); + geo_point_t p2= pc2->point(idx2); // Get wire plane IDs for the two points - auto wpid_p1 = cluster.wire_plane_id(idx1); - auto wpid_p2 = cluster.wire_plane_id(idx2); + auto wpid_p1 = cluster.wire_plane_id(pc1_global_index.at(idx1)); + auto wpid_p2 = cluster.wire_plane_id(pc2_global_index.at(idx2)); auto wpid_pc = get_wireplaneid(p1, wpid_p1, p2, wpid_p2, dv); int apa1 = wpid_p1.apa(); @@ -634,10 +635,10 @@ void Graphs::connect_graph_relaxed( // Calculate directions using VHoughTrans equivalent (vhough_transform) // Use point clouds pc1 and pc2 for local direction calculation - geo_vector_t dir1 = cluster.vhough_transform(p1, 15*units::cm, Cluster::HoughParamSpace::theta_phi); + geo_vector_t dir1 = cluster.vhough_transform(p1, 15*units::cm, Cluster::HoughParamSpace::theta_phi, pc1, pc1_global_index); dir1 = dir1 * -1; - geo_vector_t dir2 = cluster.vhough_transform(p2, 15*units::cm, Cluster::HoughParamSpace::theta_phi); + geo_vector_t dir2 = cluster.vhough_transform(p2, 15*units::cm, Cluster::HoughParamSpace::theta_phi, pc2, pc2_global_index); dir2 = dir2 * -1; geo_vector_t dir3(p1.x() - p2.x(), p1.y() - p2.y(), p1.z() - p2.z()); @@ -765,4 +766,347 @@ void Graphs::connect_graph_relaxed( } return false; - } \ No newline at end of file + } + +void Graphs::connect_graph_relaxed_pid( + const Facade::Cluster& cluster, + IDetectorVolumes::pointer dv, + IPCTransformSet::pointer pcts, + Weighted::Graph& graph){ + + // const auto* grouping = cluster.grouping(); + const geo_vector_t drift_dir_abs(1, 0, 0); + + // Form connected components + std::vector component(num_vertices(graph)); + const size_t num = connected_components(graph, &component[0]); + + if (num <= 1) return; + + // Create point clouds using connected components + std::vector> pt_clouds; + std::vector> pt_clouds_global_indices; + + // Create ordered components + std::vector ordered_components; + ordered_components.reserve(component.size()); + for (size_t i = 0; i < component.size(); ++i) { + ordered_components.emplace_back(i); + } + + // Assign vertices to components + for (size_t i = 0; i < component.size(); ++i) { + ordered_components[component[i]].add_vertex(i); + } + + // Sort components by minimum vertex index + std::sort(ordered_components.begin(), ordered_components.end(), + [](const ComponentInfo& a, const ComponentInfo& b) { + return a.min_vertex < b.min_vertex; + }); + + // Create point clouds for each component + const auto& points = cluster.points(); + for (const auto& comp : ordered_components) { + auto pt_cloud = std::make_shared(); + std::vector global_indices; + + for (size_t vertex_idx : comp.vertex_indices) { + pt_cloud->add({points[0][vertex_idx], points[1][vertex_idx], points[2][vertex_idx]}); + global_indices.push_back(vertex_idx); + } + pt_clouds.push_back(pt_cloud); + pt_clouds_global_indices.push_back(global_indices); + } + + // Initialize distance metrics + std::vector>> index_index_dis(num, std::vector>(num)); + std::vector>> index_index_dis_dir1(num, std::vector>(num)); + std::vector>> index_index_dis_dir2(num, std::vector>(num)); + + // Initialize all distances to inf + for (size_t j = 0; j != num; j++) { + for (size_t k = 0; k != num; k++) { + index_index_dis[j][k] = std::make_tuple(-1, -1, 1e9); + index_index_dis_dir1[j][k] = std::make_tuple(-1, -1, 1e9); + index_index_dis_dir2[j][k] = std::make_tuple(-1, -1, 1e9); + } + } + + // Calculate distances between components with connectivity checks + for (size_t j = 0; j != num; j++) { + for (size_t k = j + 1; k != num; k++) { + // Get closest points between components + std::tuple temp_index_index_dis = pt_clouds.at(j)->get_closest_points(*pt_clouds.at(k)); + + if (std::get<0>(temp_index_index_dis) != -1) { + index_index_dis[j][k] = temp_index_index_dis; + + // Check connectivity + bool flag = check_connectivity(cluster, dv, pcts, index_index_dis[j][k], + pt_clouds.at(j), pt_clouds_global_indices.at(j), pt_clouds.at(k), pt_clouds_global_indices.at(k)); + + // Special case for very close distances with large point clouds + if (std::get<2>(temp_index_index_dis) <= 0.9 * units::cm && + pt_clouds.at(j)->get_num_points() > 200 && + pt_clouds.at(k)->get_num_points() > 200) { + + if (!flag) { + // Try to find better connection points nearby + geo_point_t test_p1 = pt_clouds.at(k)->point(std::get<1>(temp_index_index_dis)); + geo_point_t test_p2 = pt_clouds.at(j)->point(std::get<0>(temp_index_index_dis)); + + auto temp_wcps1 = + pt_clouds.at(j)->get_closest_wcpoints_radius(test_p1, + std::get<2>(temp_index_index_dis) + 0.9 * units::cm); + auto temp_wcps2 = + pt_clouds.at(k)->get_closest_wcpoints_radius(test_p2, + std::get<2>(temp_index_index_dis) + 0.9 * units::cm); + + // Try different point combinations + for (size_t kk1 = 0; kk1 < temp_wcps1.size() && !flag; kk1++) { + for (size_t kk2 = 0; kk2 < temp_wcps2.size() && !flag; kk2++) { + double dis = std::sqrt( + std::pow(temp_wcps1[kk1].second.x() - temp_wcps2[kk2].second.x(), 2) + + std::pow(temp_wcps1[kk1].second.y() - temp_wcps2[kk2].second.y(), 2) + + std::pow(temp_wcps1[kk1].second.z() - temp_wcps2[kk2].second.z(), 2)); + + std::tuple temp_tuple = + std::make_tuple(temp_wcps2[kk2].first, temp_wcps1[kk1].first, dis); + + if (check_connectivity(cluster, dv, pcts, temp_tuple, + pt_clouds.at(j), pt_clouds_global_indices.at(j), pt_clouds.at(k), pt_clouds_global_indices.at(k), + 0.3 * units::cm, true)) { + flag = true; + index_index_dis[j][k] = temp_tuple; + break; + } + } + } + } + } + + if (!flag) { + index_index_dis[j][k] = std::make_tuple(-1, -1, 1e9); + } + index_index_dis[k][j] = index_index_dis[j][k]; + + // Calculate directional connections + if (std::get<0>(temp_index_index_dis) != -1) { + geo_point_t p1 = pt_clouds.at(j)->point(std::get<0>(temp_index_index_dis)); + geo_point_t p2 = pt_clouds.at(k)->point(std::get<1>(temp_index_index_dis)); + + // Direction from p1 + geo_vector_t dir1 = cluster.vhough_transform(p1, 30 * units::cm, + Cluster::HoughParamSpace::theta_phi, pt_clouds.at(j), pt_clouds_global_indices.at(j)); + dir1 = dir1 * -1; + + std::pair result1 = pt_clouds.at(k)->get_closest_point_along_vec( + p1, dir1, 80 * units::cm, 5 * units::cm, 7.5, 3 * units::cm); + + // If no result and perpendicular to drift, try longer hough + if (result1.first < 0 && + std::fabs(dir1.angle(drift_dir_abs) * 180.0 / M_PI - 90.0) < 10.0) { + if (std::fabs(dir1.angle(drift_dir_abs) * 180.0 / M_PI - 90.0) < 5.0) + dir1 = cluster.vhough_transform(p1, 80 * units::cm, + Cluster::HoughParamSpace::theta_phi, pt_clouds.at(j), pt_clouds_global_indices.at(j)); + else if (std::fabs(dir1.angle(drift_dir_abs) * 180.0 / M_PI - 90.0) < 10.0) + dir1 = cluster.vhough_transform(p1, 50 * units::cm, + Cluster::HoughParamSpace::theta_phi, pt_clouds.at(j), pt_clouds_global_indices.at(j)); + dir1 = dir1 * -1; + result1 = pt_clouds.at(k)->get_closest_point_along_vec( + p1, dir1, 80 * units::cm, 5 * units::cm, 7.5, 3 * units::cm); + } + + if (result1.first >= 0) { + index_index_dis_dir1[j][k] = std::make_tuple( + std::get<0>(index_index_dis[j][k]), result1.first, result1.second); + + if (!check_connectivity(cluster, dv, pcts, index_index_dis_dir1[j][k], + pt_clouds.at(j), pt_clouds_global_indices.at(j), pt_clouds.at(k), pt_clouds_global_indices.at(k))) { + index_index_dis_dir1[j][k] = std::make_tuple(-1, -1, 1e9); + } + index_index_dis_dir1[k][j] = index_index_dis_dir1[j][k]; + } + + // Direction from p2 + geo_vector_t dir2 = cluster.vhough_transform(p2, 30 * units::cm, + Cluster::HoughParamSpace::theta_phi, pt_clouds.at(k), pt_clouds_global_indices.at(k)); + dir2 = dir2 * -1; + + std::pair result2 = pt_clouds.at(j)->get_closest_point_along_vec( + p2, dir2, 80 * units::cm, 5 * units::cm, 7.5, 3 * units::cm); + + if (result2.first < 0 && + std::fabs(dir2.angle(drift_dir_abs) * 180.0 / M_PI - 90.0) < 10.0) { + if (std::fabs(dir2.angle(drift_dir_abs) * 180.0 / M_PI - 90.0) < 5.0) + dir2 = cluster.vhough_transform(p2, 80 * units::cm, + Cluster::HoughParamSpace::theta_phi, pt_clouds.at(k), pt_clouds_global_indices.at(k)); + else if (std::fabs(dir2.angle(drift_dir_abs) * 180.0 / M_PI - 90.0) < 10.0) + dir2 = cluster.vhough_transform(p2, 50 * units::cm, + Cluster::HoughParamSpace::theta_phi, pt_clouds.at(k), pt_clouds_global_indices.at(k)); + dir2 = dir2 * -1; + result2 = pt_clouds.at(j)->get_closest_point_along_vec( + p2, dir2, 80 * units::cm, 5 * units::cm, 7.5, 3 * units::cm); + } + + if (result2.first >= 0) { + index_index_dis_dir2[j][k] = std::make_tuple( + result2.first, std::get<1>(index_index_dis[j][k]), result2.second); + + if (!check_connectivity(cluster, dv, pcts, index_index_dis_dir2[j][k], + pt_clouds.at(j), pt_clouds_global_indices.at(j), pt_clouds.at(k), pt_clouds_global_indices.at(k))) { + index_index_dis_dir2[j][k] = std::make_tuple(-1, -1, 1e9); + } + index_index_dis_dir2[k][j] = index_index_dis_dir2[j][k]; + } + } + } + } + } + + // Examine middle path for all three connection types + double step_dis = 1.0 * units::cm; + + auto examine_middle_path = [&](std::vector>>& index_dis_array) { + std::map, std::set> map_add_connections; + + for (size_t j = 0; j != num; j++) { + for (size_t k = j + 1; k != num; k++) { + if (std::get<0>(index_dis_array[j][k]) >= 0) { + int idx1 = std::get<0>(index_dis_array[j][k]); + int idx2 = std::get<1>(index_dis_array[j][k]); + + geo_point_t wp1(points[0][pt_clouds_global_indices[j][idx1]], + points[1][pt_clouds_global_indices[j][idx1]], + points[2][pt_clouds_global_indices[j][idx1]]); + geo_point_t wp2(points[0][pt_clouds_global_indices[k][idx2]], + points[1][pt_clouds_global_indices[k][idx2]], + points[2][pt_clouds_global_indices[k][idx2]]); + + double length = std::sqrt( + std::pow(wp1.x() - wp2.x(), 2) + + std::pow(wp1.y() - wp2.y(), 2) + + std::pow(wp1.z() - wp2.z(), 2)); + + if (length > 3 * units::cm) { + std::set connections; + int ncount = std::round(length / step_dis); + + for (int qx = 1; qx < ncount; qx++) { + geo_point_t test_p( + wp1.x() + (wp2.x() - wp1.x()) * qx / ncount, + wp1.y() + (wp2.y() - wp1.y()) * qx / ncount, + wp1.z() + (wp2.z() - wp1.z()) * qx / ncount); + + for (size_t qx1 = 0; qx1 != num; qx1++) { + if (qx1 == j || qx1 == k) continue; + + // Skip small components, check only >= 50 points + if (pt_clouds.at(qx1)->get_closest_dis(test_p) < 0.6 * units::cm && + pt_clouds.at(qx1)->get_num_points() >= 50) { + connections.insert(qx1); + } + } + } + + if (!connections.empty()) { + map_add_connections[std::make_pair(j, k)] = connections; + } + } + } + } + } + + // Iteratively disconnect paths blocked by intermediate components + bool flag_continue = true; + while (flag_continue) { + flag_continue = false; + std::set> used_pairs; + + for (auto it = map_add_connections.begin(); it != map_add_connections.end(); it++) { + int j = it->first.first; + int k = it->first.second; + bool flag_disconnect = true; + + for (auto it1 = it->second.begin(); it1 != it->second.end(); it1++) { + int qx = *it1; + if ((std::get<0>(index_index_dis[j][qx]) != -1 || + std::get<0>(index_index_dis_dir1[j][qx]) != -1 || + std::get<0>(index_index_dis_dir2[j][qx]) != -1) && + (std::get<0>(index_index_dis[k][qx]) != -1 || + std::get<0>(index_index_dis_dir1[k][qx]) != -1 || + std::get<0>(index_index_dis_dir2[k][qx]) != -1)) { + flag_disconnect = false; + break; + } + } + + if (flag_disconnect) { + flag_continue = true; + index_dis_array[j][k] = std::make_tuple(-1, -1, 1e9); + index_dis_array[k][j] = index_dis_array[j][k]; + used_pairs.insert(it->first); + } + } + + for (auto it = used_pairs.begin(); it != used_pairs.end(); it++) { + map_add_connections.erase(*it); + } + } + }; + + // Examine all three connection types + examine_middle_path(index_index_dis); + examine_middle_path(index_index_dis_dir1); + examine_middle_path(index_index_dis_dir2); + + // Final assembly: add edges to graph + for (size_t j = 0; j != num; j++) { + for (size_t k = j + 1; k != num; k++) { + // Add closest distance connections + if (std::get<0>(index_index_dis[j][k]) >= 0) { + const int gind1 = pt_clouds_global_indices.at(j).at(std::get<0>(index_index_dis[j][k])); + const int gind2 = pt_clouds_global_indices.at(k).at(std::get<1>(index_index_dis[j][k])); + + if (!boost::edge(gind1, gind2, graph).second) { + add_edge(gind1, gind2, std::get<2>(index_index_dis[j][k]), graph); + } + } + + // Add directional connection 1 + if (std::get<0>(index_index_dis_dir1[j][k]) >= 0) { + const int gind1 = pt_clouds_global_indices.at(j).at(std::get<0>(index_index_dis_dir1[j][k])); + const int gind2 = pt_clouds_global_indices.at(k).at(std::get<1>(index_index_dis_dir1[j][k])); + + float dis; + if (std::get<2>(index_index_dis_dir1[j][k]) > 5 * units::cm) { + dis = std::get<2>(index_index_dis_dir1[j][k]) * 1.2; + } else { + dis = std::get<2>(index_index_dis_dir1[j][k]); + } + + if (!boost::edge(gind1, gind2, graph).second) { + add_edge(gind1, gind2, dis, graph); + } + } + + // Add directional connection 2 + if (std::get<0>(index_index_dis_dir2[j][k]) >= 0) { + const int gind1 = pt_clouds_global_indices.at(j).at(std::get<0>(index_index_dis_dir2[j][k])); + const int gind2 = pt_clouds_global_indices.at(k).at(std::get<1>(index_index_dis_dir2[j][k])); + + float dis; + if (std::get<2>(index_index_dis_dir2[j][k]) > 5 * units::cm) { + dis = std::get<2>(index_index_dis_dir2[j][k]) * 1.2; + } else { + dis = std::get<2>(index_index_dis_dir2[j][k]); + } + + if (!boost::edge(gind1, gind2, graph).second) { + add_edge(gind1, gind2, dis, graph); + } + } + } + } +} \ No newline at end of file diff --git a/clus/src/connect_graphs.h b/clus/src/connect_graphs.h index 872135b2..6a04d8f1 100644 --- a/clus/src/connect_graphs.h +++ b/clus/src/connect_graphs.h @@ -44,12 +44,19 @@ namespace WireCell::Clus::Graphs { IPCTransformSet::pointer pcts, Weighted::Graph& graph); + void connect_graph_relaxed_pid( + const Facade::Cluster& cluster, + IDetectorVolumes::pointer dv, + IPCTransformSet::pointer pcts, + Weighted::Graph& graph); + bool is_point_good(const Facade::Cluster& cluster, size_t point_index, int ncut = 3); std::vector check_direction(const Facade::Cluster& cluster, Facade::geo_vector_t& v1, int apa, int face, double angle_cut_1 = 12.5, double angle_cut_2 = 10); - + bool check_connectivity(const Facade::Cluster& cluster, IDetectorVolumes::pointer dv, - IPCTransformSet::pointer pcts, std::tuple& index_index_dis, std::shared_ptr pc1, std::shared_ptr pc2, double step_size = 0.6*units::cm, bool flag_strong_check = false); + IPCTransformSet::pointer pcts, std::tuple& index_index_dis, std::shared_ptr pc1, std::vector pc1_global_index, std::shared_ptr pc2, std::vector pc2_global_index, + double step_size = 0.6*units::cm, bool flag_strong_check = false); } #endif From d999e6a4552420becc59cd95a60c258fef493554 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Thu, 27 Nov 2025 11:00:16 -0800 Subject: [PATCH 025/111] implement the new graph --- clus/src/make_graphs.cxx | 10 ++++++++++ clus/src/make_graphs.h | 5 +++++ 2 files changed, 15 insertions(+) diff --git a/clus/src/make_graphs.cxx b/clus/src/make_graphs.cxx index abf93758..1ea7208e 100644 --- a/clus/src/make_graphs.cxx +++ b/clus/src/make_graphs.cxx @@ -84,3 +84,13 @@ Weighted::Graph WireCell::Clus::Graphs::make_graph_relaxed( connect_graph_relaxed(cluster, dv, pcts, graph); return graph; } + +Weighted::Graph WireCell::Clus::Graphs::make_graph_relaxed_pid( + const Facade::Cluster& cluster, + IDetectorVolumes::pointer dv, + IPCTransformSet::pointer pcts) +{ + auto graph = make_graph_closely_pid(cluster); + connect_graph_relaxed_pid(cluster, dv, pcts, graph); + return graph; +} diff --git a/clus/src/make_graphs.h b/clus/src/make_graphs.h index f44c6e27..69f7663b 100644 --- a/clus/src/make_graphs.h +++ b/clus/src/make_graphs.h @@ -47,6 +47,11 @@ namespace WireCell::Clus::Graphs { IDetectorVolumes::pointer dv, IPCTransformSet::pointer pcts); + Weighted::Graph make_graph_relaxed_pid( + const Facade::Cluster& cluster, + IDetectorVolumes::pointer dv, + IPCTransformSet::pointer pcts); + } #endif From 3397df4f0e8d9b33a1b6f133ace8de5e5267c375 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sat, 29 Nov 2025 13:50:13 -0800 Subject: [PATCH 026/111] prepare files for Pattern Recognition --- clus/inc/WireCellClus/NeutrinoDLVertex.h | 0 clus/inc/WireCellClus/NeutrinoDeghoster.h | 0 clus/inc/WireCellClus/NeutrinoEnergyReco.h | 0 clus/inc/WireCellClus/NeutrinoKinematics.h | 0 clus/inc/WireCellClus/NeutrinoPatternBase.h | 0 clus/inc/WireCellClus/NeutrinoStructureExaminer.h | 0 clus/inc/WireCellClus/NeutrinoTaggerCosmic.h | 0 clus/inc/WireCellClus/NeutrinoTaggerNuE.h | 0 clus/inc/WireCellClus/NeutrinoTaggerNuMu.h | 0 clus/inc/WireCellClus/NeutrinoTaggerPi0.h | 0 clus/inc/WireCellClus/NeutrinoTaggerSSM.h | 0 clus/inc/WireCellClus/NeutrinoTaggerSinglePhoton.h | 0 clus/inc/WireCellClus/NeutrinoTrackShowerSep.h | 0 clus/inc/WireCellClus/NeutrinoVertexFinder.h | 0 clus/src/NeutrinoDLVertex.cxx | 0 clus/src/NeutrinoDeghoster.cxx | 0 clus/src/NeutrinoEnergyReco.cxx | 0 clus/src/NeutrinoKinematics.cxx | 0 clus/src/NeutrinoPatternBase.cxx | 0 clus/src/NeutrinoStructureExaminer.cxx | 0 clus/src/NeutrinoTaggerCosmic.cxx | 0 clus/src/NeutrinoTaggerNuE.cxx | 0 clus/src/NeutrinoTaggerNuMu.cxx | 0 clus/src/NeutrinoTaggerPi0.cxx | 0 clus/src/NeutrinoTaggerSSM.cxx | 0 clus/src/NeutrinoTaggerSinglePhoton.cxx | 0 clus/src/NeutrinoTrackShowerSep.cxx | 0 clus/src/NeutrinoVertexFinder.cxx | 0 28 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 clus/inc/WireCellClus/NeutrinoDLVertex.h create mode 100644 clus/inc/WireCellClus/NeutrinoDeghoster.h create mode 100644 clus/inc/WireCellClus/NeutrinoEnergyReco.h create mode 100644 clus/inc/WireCellClus/NeutrinoKinematics.h create mode 100644 clus/inc/WireCellClus/NeutrinoPatternBase.h create mode 100644 clus/inc/WireCellClus/NeutrinoStructureExaminer.h create mode 100644 clus/inc/WireCellClus/NeutrinoTaggerCosmic.h create mode 100644 clus/inc/WireCellClus/NeutrinoTaggerNuE.h create mode 100644 clus/inc/WireCellClus/NeutrinoTaggerNuMu.h create mode 100644 clus/inc/WireCellClus/NeutrinoTaggerPi0.h create mode 100644 clus/inc/WireCellClus/NeutrinoTaggerSSM.h create mode 100644 clus/inc/WireCellClus/NeutrinoTaggerSinglePhoton.h create mode 100644 clus/inc/WireCellClus/NeutrinoTrackShowerSep.h create mode 100644 clus/inc/WireCellClus/NeutrinoVertexFinder.h create mode 100644 clus/src/NeutrinoDLVertex.cxx create mode 100644 clus/src/NeutrinoDeghoster.cxx create mode 100644 clus/src/NeutrinoEnergyReco.cxx create mode 100644 clus/src/NeutrinoKinematics.cxx create mode 100644 clus/src/NeutrinoPatternBase.cxx create mode 100644 clus/src/NeutrinoStructureExaminer.cxx create mode 100644 clus/src/NeutrinoTaggerCosmic.cxx create mode 100644 clus/src/NeutrinoTaggerNuE.cxx create mode 100644 clus/src/NeutrinoTaggerNuMu.cxx create mode 100644 clus/src/NeutrinoTaggerPi0.cxx create mode 100644 clus/src/NeutrinoTaggerSSM.cxx create mode 100644 clus/src/NeutrinoTaggerSinglePhoton.cxx create mode 100644 clus/src/NeutrinoTrackShowerSep.cxx create mode 100644 clus/src/NeutrinoVertexFinder.cxx diff --git a/clus/inc/WireCellClus/NeutrinoDLVertex.h b/clus/inc/WireCellClus/NeutrinoDLVertex.h new file mode 100644 index 00000000..e69de29b diff --git a/clus/inc/WireCellClus/NeutrinoDeghoster.h b/clus/inc/WireCellClus/NeutrinoDeghoster.h new file mode 100644 index 00000000..e69de29b diff --git a/clus/inc/WireCellClus/NeutrinoEnergyReco.h b/clus/inc/WireCellClus/NeutrinoEnergyReco.h new file mode 100644 index 00000000..e69de29b diff --git a/clus/inc/WireCellClus/NeutrinoKinematics.h b/clus/inc/WireCellClus/NeutrinoKinematics.h new file mode 100644 index 00000000..e69de29b diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h new file mode 100644 index 00000000..e69de29b diff --git a/clus/inc/WireCellClus/NeutrinoStructureExaminer.h b/clus/inc/WireCellClus/NeutrinoStructureExaminer.h new file mode 100644 index 00000000..e69de29b diff --git a/clus/inc/WireCellClus/NeutrinoTaggerCosmic.h b/clus/inc/WireCellClus/NeutrinoTaggerCosmic.h new file mode 100644 index 00000000..e69de29b diff --git a/clus/inc/WireCellClus/NeutrinoTaggerNuE.h b/clus/inc/WireCellClus/NeutrinoTaggerNuE.h new file mode 100644 index 00000000..e69de29b diff --git a/clus/inc/WireCellClus/NeutrinoTaggerNuMu.h b/clus/inc/WireCellClus/NeutrinoTaggerNuMu.h new file mode 100644 index 00000000..e69de29b diff --git a/clus/inc/WireCellClus/NeutrinoTaggerPi0.h b/clus/inc/WireCellClus/NeutrinoTaggerPi0.h new file mode 100644 index 00000000..e69de29b diff --git a/clus/inc/WireCellClus/NeutrinoTaggerSSM.h b/clus/inc/WireCellClus/NeutrinoTaggerSSM.h new file mode 100644 index 00000000..e69de29b diff --git a/clus/inc/WireCellClus/NeutrinoTaggerSinglePhoton.h b/clus/inc/WireCellClus/NeutrinoTaggerSinglePhoton.h new file mode 100644 index 00000000..e69de29b diff --git a/clus/inc/WireCellClus/NeutrinoTrackShowerSep.h b/clus/inc/WireCellClus/NeutrinoTrackShowerSep.h new file mode 100644 index 00000000..e69de29b diff --git a/clus/inc/WireCellClus/NeutrinoVertexFinder.h b/clus/inc/WireCellClus/NeutrinoVertexFinder.h new file mode 100644 index 00000000..e69de29b diff --git a/clus/src/NeutrinoDLVertex.cxx b/clus/src/NeutrinoDLVertex.cxx new file mode 100644 index 00000000..e69de29b diff --git a/clus/src/NeutrinoDeghoster.cxx b/clus/src/NeutrinoDeghoster.cxx new file mode 100644 index 00000000..e69de29b diff --git a/clus/src/NeutrinoEnergyReco.cxx b/clus/src/NeutrinoEnergyReco.cxx new file mode 100644 index 00000000..e69de29b diff --git a/clus/src/NeutrinoKinematics.cxx b/clus/src/NeutrinoKinematics.cxx new file mode 100644 index 00000000..e69de29b diff --git a/clus/src/NeutrinoPatternBase.cxx b/clus/src/NeutrinoPatternBase.cxx new file mode 100644 index 00000000..e69de29b diff --git a/clus/src/NeutrinoStructureExaminer.cxx b/clus/src/NeutrinoStructureExaminer.cxx new file mode 100644 index 00000000..e69de29b diff --git a/clus/src/NeutrinoTaggerCosmic.cxx b/clus/src/NeutrinoTaggerCosmic.cxx new file mode 100644 index 00000000..e69de29b diff --git a/clus/src/NeutrinoTaggerNuE.cxx b/clus/src/NeutrinoTaggerNuE.cxx new file mode 100644 index 00000000..e69de29b diff --git a/clus/src/NeutrinoTaggerNuMu.cxx b/clus/src/NeutrinoTaggerNuMu.cxx new file mode 100644 index 00000000..e69de29b diff --git a/clus/src/NeutrinoTaggerPi0.cxx b/clus/src/NeutrinoTaggerPi0.cxx new file mode 100644 index 00000000..e69de29b diff --git a/clus/src/NeutrinoTaggerSSM.cxx b/clus/src/NeutrinoTaggerSSM.cxx new file mode 100644 index 00000000..e69de29b diff --git a/clus/src/NeutrinoTaggerSinglePhoton.cxx b/clus/src/NeutrinoTaggerSinglePhoton.cxx new file mode 100644 index 00000000..e69de29b diff --git a/clus/src/NeutrinoTrackShowerSep.cxx b/clus/src/NeutrinoTrackShowerSep.cxx new file mode 100644 index 00000000..e69de29b diff --git a/clus/src/NeutrinoVertexFinder.cxx b/clus/src/NeutrinoVertexFinder.cxx new file mode 100644 index 00000000..e69de29b From 4a37136f534f25e79e28c8d40b316f0645e92a45 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sat, 29 Nov 2025 14:53:20 -0800 Subject: [PATCH 027/111] add a function --- clus/inc/WireCellClus/PRGraph.h | 12 ++++++++++-- clus/src/PRGraph.cxx | 18 +++++++++++++++++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/clus/inc/WireCellClus/PRGraph.h b/clus/inc/WireCellClus/PRGraph.h index 33334259..3b9ad856 100644 --- a/clus/inc/WireCellClus/PRGraph.h +++ b/clus/inc/WireCellClus/PRGraph.h @@ -115,7 +115,7 @@ namespace WireCell::Clus::PR { /// The two Vertex objects are those associated with the source/target nodes /// of the segment's edge. The pair is ordered. The first Vertex is the /// one with a "wcpoint" closest to the segment's initial "wcpoint". - std::pair find_endpoints(Graph& graph, SegmentPtr seg); + std::pair find_vertices(Graph& graph, SegmentPtr seg); /// Return the other vertex connected to a segment, given one known vertex. /// @@ -127,7 +127,15 @@ namespace WireCell::Clus::PR { /// the vertex at the other end. VertexPtr find_other_vertex(Graph& graph, SegmentPtr seg, VertexPtr vertex); - + /// Find the segment connecting two vertices. + /// + /// Returns nullptr if: + /// - Either vertex is not in the graph + /// - No edge exists between the two vertices + /// + /// This function searches for an edge between the two given vertices and + /// returns the associated segment if found. + SegmentPtr find_segment(Graph& graph, VertexPtr vtx1, VertexPtr vtx2); }; #endif diff --git a/clus/src/PRGraph.cxx b/clus/src/PRGraph.cxx index 3fe00ba8..2d5e364d 100644 --- a/clus/src/PRGraph.cxx +++ b/clus/src/PRGraph.cxx @@ -65,7 +65,7 @@ namespace WireCell::Clus::PR { } - std::pair find_endpoints(Graph& graph, SegmentPtr seg) + std::pair find_vertices(Graph& graph, SegmentPtr seg) { if (! seg->descriptor_valid()) { return std::pair{}; } @@ -117,6 +117,22 @@ namespace WireCell::Clus::PR { return nullptr; } + SegmentPtr find_segment(Graph& graph, VertexPtr vtx1, VertexPtr vtx2) + { + if (! vtx1->descriptor_valid()) { return nullptr; } + if (! vtx2->descriptor_valid()) { return nullptr; } + + auto vd1 = vtx1->get_descriptor(); + auto vd2 = vtx2->get_descriptor(); + + // Check if edge exists between the two vertices + auto [ed, exists] = boost::edge(vd1, vd2, graph); + if (!exists) { return nullptr; } + + // Return the segment associated with this edge + return graph[ed].segment; + } + } From b531f0a7ff2d1ecd8ecd4a73ab06cd6fedc02f85 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sat, 29 Nov 2025 15:20:58 -0800 Subject: [PATCH 028/111] implement some functions --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 11 ++++ clus/src/NeutrinoPatternBase.cxx | 64 +++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index e69de29b..ab25f04c 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -0,0 +1,11 @@ +#include "WireCellClus/PRGraph.h" +#include "WireCellClus/Facade_Cluster.h" + +namespace WireCell::Clus::PR { + class PatternAlgorithms{ + public: + std::set find_vertices(Graph& graph, const Facade::Cluster& cluster); + std::set find_segments(Graph& graph, const Facade::Cluster& cluster); + bool clean_up_graph(Graph& graph, const Facade::Cluster& cluster); + }; +} diff --git a/clus/src/NeutrinoPatternBase.cxx b/clus/src/NeutrinoPatternBase.cxx index e69de29b..d8dcd77e 100644 --- a/clus/src/NeutrinoPatternBase.cxx +++ b/clus/src/NeutrinoPatternBase.cxx @@ -0,0 +1,64 @@ +#include "WireCellClus/NeutrinoPatternBase.h" + +using namespace WireCell::Clus::PR; + +std::set PatternAlgorithms::find_vertices(Graph& graph, const Facade::Cluster& cluster) +{ + std::set result; + + // Iterate through all vertices in the graph + auto [vbegin, vend] = boost::vertices(graph); + for (auto vit = vbegin; vit != vend; ++vit) { + VertexPtr vtx = graph[*vit].vertex; + + // Check if this vertex belongs to the specified cluster + if (vtx && vtx->cluster() && vtx->cluster() == &cluster) { + result.insert(vtx); + } + } + + return result; +} + +std::set PatternAlgorithms::find_segments(Graph& graph, const Facade::Cluster& cluster) +{ + std::set result; + + // Iterate through all edges (segments) in the graph + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr seg = graph[*eit].segment; + + // Check if this segment belongs to the specified cluster + if (seg && seg->cluster() && seg->cluster() == &cluster) { + result.insert(seg); + } + } + + return result; +} + +bool PatternAlgorithms::clean_up_graph(Graph& graph, const Facade::Cluster& cluster) +{ + bool modified = false; + + // First, find and remove all segments associated with this cluster + std::set segments_to_remove = find_segments(graph, cluster); + for (auto seg : segments_to_remove) { + if (remove_segment(graph, seg)) { + modified = true; + } + } + + // Then, find and remove all vertices associated with this cluster + // Note: vertices that are still connected to other segments won't be removed + // until their segments are removed first + std::set vertices_to_remove = find_vertices(graph, cluster); + for (auto vtx : vertices_to_remove) { + if (remove_vertex(graph, vtx)) { + modified = true; + } + } + + return modified; +} \ No newline at end of file From 87aea6e242785fedb4d8d977f1d32e94a9ac32c6 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sat, 29 Nov 2025 16:53:08 -0800 Subject: [PATCH 029/111] put in a template --- clus/inc/WireCellClus/TaggerCheckNeutrino.h | 42 ++++++++ clus/src/TaggerCheckNeutrino.cxx | 108 ++++++++++++++++++++ 2 files changed, 150 insertions(+) create mode 100644 clus/inc/WireCellClus/TaggerCheckNeutrino.h create mode 100644 clus/src/TaggerCheckNeutrino.cxx diff --git a/clus/inc/WireCellClus/TaggerCheckNeutrino.h b/clus/inc/WireCellClus/TaggerCheckNeutrino.h new file mode 100644 index 00000000..5ac69423 --- /dev/null +++ b/clus/inc/WireCellClus/TaggerCheckNeutrino.h @@ -0,0 +1,42 @@ +#include "WireCellClus/IEnsembleVisitor.h" +#include "WireCellClus/ClusteringFuncs.h" +#include "WireCellClus/ClusteringFuncsMixins.h" +#include "WireCellClus/ParticleDataSet.h" +#include "WireCellClus/FiducialUtils.h" +#include "WireCellIface/IConfigurable.h" +#include "WireCellUtil/NamedFactory.h" +#include "WireCellUtil/Logging.h" +#include "WireCellClus/PRGraph.h" +#include "WireCellClus/TrackFitting.h" +#include "WireCellClus/TrackFittingPresets.h" +#include "WireCellClus/PRSegmentFunctions.h" + +#include "WireCellIface/IScalarFunction.h" +#include "WireCellUtil/KSTest.h" + +using namespace WireCell; +using namespace WireCell::Clus; +using namespace WireCell::Clus::Facade; + +class TaggerCheckNeutrino : public IConfigurable, public Clus::IEnsembleVisitor, private Clus::NeedDV, private Clus::NeedPCTS, private Clus::NeedRecombModel, private Clus::NeedParticleData { +public: + TaggerCheckNeutrino() { + // Initialize with default preset + m_track_fitter = TrackFittingPresets::create_with_current_values(); + } + virtual ~TaggerCheckNeutrino() {} + virtual void configure(const WireCell::Configuration& config) ; + + virtual Configuration default_configuration() const ; + + virtual void visit(Ensemble& ensemble) const; + + + private: + std::string m_grouping_name{"live"}; + std::string m_trackfitting_config_file; // Path to TrackFitting config file + mutable TrackFitting m_track_fitter; + + void load_trackfitting_config(const std::string& config_file); + +}; \ No newline at end of file diff --git a/clus/src/TaggerCheckNeutrino.cxx b/clus/src/TaggerCheckNeutrino.cxx new file mode 100644 index 00000000..21d7a47d --- /dev/null +++ b/clus/src/TaggerCheckNeutrino.cxx @@ -0,0 +1,108 @@ +#include "WireCellClus/TaggerCheckNeutrino.h" + +class TaggerCheckNeutrino; +WIRECELL_FACTORY(TaggerCheckNeutrino, TaggerCheckNeutrino, + WireCell::IConfigurable, WireCell::Clus::IEnsembleVisitor) + +using namespace WireCell; +using namespace WireCell::Clus; +using namespace WireCell::Clus::Facade; + +struct edge_base_t { + typedef boost::edge_property_tag kind; +}; + +void TaggerCheckNeutrino::configure(const WireCell::Configuration& config) +{ + m_grouping_name = get(config, "grouping_name", m_grouping_name); + m_trackfitting_config_file = get(config, "trackfitting_config_file", m_trackfitting_config_file); + + if (!m_trackfitting_config_file.empty()) { + load_trackfitting_config(m_trackfitting_config_file); + } + + NeedDV::configure(config); + NeedPCTS::configure(config); + NeedRecombModel::configure(config); + NeedParticleData::configure(config); +} + +Configuration TaggerCheckNeutrino::default_configuration() const +{ + Configuration cfg; + cfg["grouping"] = m_grouping_name; + cfg["detector_volumes"] = "DetectorVolumes"; + cfg["pc_transforms"] = "PCTransformSet"; + cfg["recombination_model"] = "BoxRecombination"; + cfg["particle_dataset"] = "ParticleDataSet"; + + cfg["trackfitting_config_file"] = ""; + + return cfg; +} + +void TaggerCheckNeutrino::visit(Ensemble& ensemble) const +{ + // Configure the track fitter with detector volume + m_track_fitter.set_detector_volume(m_dv); + m_track_fitter.set_pc_transforms(m_pcts); + + // Get the specified grouping (default: "live") + auto groupings = ensemble.with_name(m_grouping_name); + if (groupings.empty()) { + return; + } + + auto& grouping = *groupings.at(0); + + // Find clusters that have the main_cluster flag (set by clustering_recovering_bundle) + Cluster* main_cluster = nullptr; + + for (auto* cluster : grouping.children()) { + if (cluster->get_flag(Flags::main_cluster)) { + main_cluster = cluster; + } + } +} + +void TaggerCheckNeutrino::load_trackfitting_config(const std::string& config_file) +{ + try { + // Load JSON file + std::ifstream file(config_file); + if (!file.is_open()) { + std::cerr << "TaggerCheckSTM: Cannot open config file: " << config_file << std::endl; + return; + } + + Json::Value root; + Json::CharReaderBuilder builder; + std::string errs; + + if (!Json::parseFromStream(builder, file, &root, &errs)) { + std::cerr << "TaggerCheckSTM: Failed to parse JSON: " << errs << std::endl; + return; + } + + // Apply each parameter from the JSON file + for (const auto& param_name : root.getMemberNames()) { + if (param_name.substr(0, 1) == "_") continue; // Skip comments + + try { + double value = root[param_name].asDouble(); + m_track_fitter.set_parameter(param_name, value); + std::cout << "TaggerCheckSTM: Set " << param_name << " = " << value << std::endl; + } catch (const std::exception& e) { + std::cerr << "TaggerCheckSTM: Failed to set parameter " << param_name + << ": " << e.what() << std::endl; + } + } + + std::cout << "TaggerCheckSTM: Successfully loaded TrackFitting configuration" << std::endl; + + } catch (const std::exception& e) { + std::cerr << "TaggerCheckSTM: Exception loading config: " << e.what() << std::endl; + std::cerr << "TaggerCheckSTM: Using default TrackFitting parameters" << std::endl; + } +} + From df694dfc48e1f1503012bd9e7da171de05bf56d1 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sat, 29 Nov 2025 17:01:31 -0800 Subject: [PATCH 030/111] catch up --- cfg/pgrapher/common/clus.jsonnet | 11 +++++++++++ clus/src/TaggerCheckNeutrino.cxx | 14 +++++++------- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/cfg/pgrapher/common/clus.jsonnet b/cfg/pgrapher/common/clus.jsonnet index b466c51f..32cbe49e 100644 --- a/cfg/pgrapher/common/clus.jsonnet +++ b/cfg/pgrapher/common/clus.jsonnet @@ -94,6 +94,17 @@ local wc = import "wirecell.jsonnet"; } + dv_cfg + pcts_cfg }, + tagger_check_neutrino(name="", trackfitting_config_file="", particle_dataset="", recombination_model="") :: { + type: "TaggerCheckNeutrino", + name: prefix + name, + data: { + grouping: "live", // Which grouping to process + trackfitting_config_file: trackfitting_config_file, + particle_dataset: particle_dataset, + recombination_model: recombination_model, + } + dv_cfg + pcts_cfg + }, + pointed(name="", groupings=["live"]) :: { type: "ClusteringPointed", name: prefix+name, diff --git a/clus/src/TaggerCheckNeutrino.cxx b/clus/src/TaggerCheckNeutrino.cxx index 21d7a47d..571bfae8 100644 --- a/clus/src/TaggerCheckNeutrino.cxx +++ b/clus/src/TaggerCheckNeutrino.cxx @@ -71,7 +71,7 @@ void TaggerCheckNeutrino::load_trackfitting_config(const std::string& config_fil // Load JSON file std::ifstream file(config_file); if (!file.is_open()) { - std::cerr << "TaggerCheckSTM: Cannot open config file: " << config_file << std::endl; + std::cerr << "TaggerCheckNeutrino: Cannot open config file: " << config_file << std::endl; return; } @@ -80,7 +80,7 @@ void TaggerCheckNeutrino::load_trackfitting_config(const std::string& config_fil std::string errs; if (!Json::parseFromStream(builder, file, &root, &errs)) { - std::cerr << "TaggerCheckSTM: Failed to parse JSON: " << errs << std::endl; + std::cerr << "TaggerCheckNeutrino: Failed to parse JSON: " << errs << std::endl; return; } @@ -91,18 +91,18 @@ void TaggerCheckNeutrino::load_trackfitting_config(const std::string& config_fil try { double value = root[param_name].asDouble(); m_track_fitter.set_parameter(param_name, value); - std::cout << "TaggerCheckSTM: Set " << param_name << " = " << value << std::endl; + std::cout << "TaggerCheckNeutrino: Set " << param_name << " = " << value << std::endl; } catch (const std::exception& e) { - std::cerr << "TaggerCheckSTM: Failed to set parameter " << param_name + std::cerr << "TaggerCheckNeutrino: Failed to set parameter " << param_name << ": " << e.what() << std::endl; } } - std::cout << "TaggerCheckSTM: Successfully loaded TrackFitting configuration" << std::endl; + std::cout << "TaggerCheckNeutrino: Successfully loaded TrackFitting configuration" << std::endl; } catch (const std::exception& e) { - std::cerr << "TaggerCheckSTM: Exception loading config: " << e.what() << std::endl; - std::cerr << "TaggerCheckSTM: Using default TrackFitting parameters" << std::endl; + std::cerr << "TaggerCheckNeutrino: Exception loading config: " << e.what() << std::endl; + std::cerr << "TaggerCheckNeutrino: Using default TrackFitting parameters" << std::endl; } } From dea665a8701dba4aef06467ffc08ad932ffaecab Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sun, 30 Nov 2025 07:59:18 -0800 Subject: [PATCH 031/111] implement init_first_segment --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 8 + clus/src/NeutrinoPatternBase.cxx | 174 ++++++++++++++++++++ clus/src/TaggerCheckNeutrino.cxx | 9 + 3 files changed, 191 insertions(+) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index ab25f04c..c2c5b247 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -1,5 +1,6 @@ #include "WireCellClus/PRGraph.h" #include "WireCellClus/Facade_Cluster.h" +#include "WireCellClus/TrackFitting.h" namespace WireCell::Clus::PR { class PatternAlgorithms{ @@ -7,5 +8,12 @@ namespace WireCell::Clus::PR { std::set find_vertices(Graph& graph, const Facade::Cluster& cluster); std::set find_segments(Graph& graph, const Facade::Cluster& cluster); bool clean_up_graph(Graph& graph, const Facade::Cluster& cluster); + + SegmentPtr init_first_segment(Graph& graph, Facade::Cluster& cluster, Facade::Cluster* main_cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, bool flag_back_search = true); + + // find the shortest path using steiner graph + std::vector do_rough_path(const Facade::Cluster& cluster,Facade::geo_point_t& first_point, Facade::geo_point_t& last_point); + + SegmentPtr create_segment_for_cluster(WireCell::Clus::Facade::Cluster& cluster, IDetectorVolumes::pointer dv, const std::vector& path_points, int dir); }; } diff --git a/clus/src/NeutrinoPatternBase.cxx b/clus/src/NeutrinoPatternBase.cxx index d8dcd77e..0c09b0df 100644 --- a/clus/src/NeutrinoPatternBase.cxx +++ b/clus/src/NeutrinoPatternBase.cxx @@ -1,6 +1,8 @@ #include "WireCellClus/NeutrinoPatternBase.h" +#include "WireCellClus/PRSegmentFunctions.h" using namespace WireCell::Clus::PR; +using namespace WireCell::Clus; std::set PatternAlgorithms::find_vertices(Graph& graph, const Facade::Cluster& cluster) { @@ -61,4 +63,176 @@ bool PatternAlgorithms::clean_up_graph(Graph& graph, const Facade::Cluster& clus } return modified; +} + +std::vector PatternAlgorithms::do_rough_path(const Facade::Cluster& cluster,Facade::geo_point_t& first_point, Facade::geo_point_t& last_point){ + // Find closest indices in the steiner point cloud + auto first_knn_results = cluster.kd_steiner_knn(1, first_point, "steiner_pc"); + auto last_knn_results = cluster.kd_steiner_knn(1, last_point, "steiner_pc"); + + auto first_index = first_knn_results[0].first; // Get the index from the first result + auto last_index = last_knn_results[0].first; // Get the index from the first result + + // 4. Use Steiner graph to find the shortest path + const std::vector& path_indices = + cluster.graph_algorithms("steiner_graph").shortest_path(first_index, last_index); + + std::vector path_points; + const auto& steiner_pc = cluster.get_pc("steiner_pc"); + const auto& coords = cluster.get_default_scope().coords; + const auto& x_coords = steiner_pc.get(coords.at(0))->elements(); + const auto& y_coords = steiner_pc.get(coords.at(1))->elements(); + const auto& z_coords = steiner_pc.get(coords.at(2))->elements(); + + for (size_t idx : path_indices) { + path_points.emplace_back(x_coords[idx], y_coords[idx], z_coords[idx]); + } + return path_points; +} + +SegmentPtr PatternAlgorithms::create_segment_for_cluster(WireCell::Clus::Facade::Cluster& cluster, IDetectorVolumes::pointer dv, const std::vector& path_points, int dir){ + // Step 3: Prepare segment data + std::vector wcpoints; + // const auto transform = m_pcts->pc_transform(cluster.get_scope_transform(cluster.get_default_scope())); + // Step 4: Create segment connecting the vertices + auto segment = PR::make_segment(); + + // create and associate Dynamic Point Cloud + for (const auto& point : path_points) { + PR::WCPoint wcp; + wcp.point = point; + wcpoints.push_back(wcp); + } + + // Step 5: Configure the segment + segment->wcpts(wcpoints).cluster(&cluster).dirsign(dir); // direction: +1, 0, or -1 + + // auto& wcpts = segment->wcpts(); + // for (size_t i=0;i!=path_points.size(); i++){ + // std::cout << "A: " << i << " " << path_points.at(i) << " " << wcpts.at(i).point << std::endl; + // } + create_segment_point_cloud(segment, path_points, dv, "main"); + + return segment; +} + + +SegmentPtr PatternAlgorithms::init_first_segment(Graph& graph, Facade::Cluster& cluster, Facade::Cluster* main_cluster,TrackFitting& track_fitter, IDetectorVolumes::pointer dv, bool flag_back_search) +{ + // Get two boundary points from the cluster + auto boundary_indices = cluster.get_two_boundary_steiner_graph_idx("steiner_graph", "steiner_pc"); + + const auto& steiner_pc = cluster.get_pc("steiner_pc"); + const auto& coords = cluster.get_default_scope().coords; + const auto& x_coords = steiner_pc.get(coords.at(0))->elements(); + const auto& y_coords = steiner_pc.get(coords.at(1))->elements(); + const auto& z_coords = steiner_pc.get(coords.at(2))->elements(); + + // Add the two boundary points as additional extreme point groups + Facade::geo_point_t boundary_point_first(x_coords[boundary_indices.first], + y_coords[boundary_indices.first], + z_coords[boundary_indices.first]); + Facade::geo_point_t boundary_point_second(x_coords[boundary_indices.second], + y_coords[boundary_indices.second], + z_coords[boundary_indices.second]); + Facade::geo_point_t first_pt = boundary_point_first; + Facade::geo_point_t second_pt = boundary_point_second; + + // Determine the starting point based on whether this is the main cluster or not + if (cluster.get_flag(Facade::Flags::main_cluster)) { + // Main cluster: start from downstream (or upstream if flag_back_search) + if (flag_back_search) { + // Start from high z (upstream/backward) + if (first_pt.z() < second_pt.z()) { + std::swap(first_pt, second_pt); + } + } else { + // Start from low z (downstream/forward) + if (first_pt.z() > second_pt.z()) { + std::swap(first_pt, second_pt); + } + } + } else{ + // Non-main cluster: start from the point closest to main cluster + auto main_steiner_pc = main_cluster->get_pc("steiner_pc"); + // Find closest distances to main cluster's Steiner point cloud + auto knn1 = main_cluster->kd_steiner_knn(1, first_pt, "steiner_pc"); + auto knn2 = main_cluster->kd_steiner_knn(1, second_pt, "steiner_pc"); + + double dis1 = std::sqrt(knn1[0].second); + double dis2 = std::sqrt(knn2[0].second); + + // Start from the point closer to main cluster + if (dis2 < dis1) { + std::swap(first_pt, second_pt); + } + } + + // Create vertices for the endpoints + VertexPtr v1 = make_vertex(graph); + v1->wcpt().point = first_pt; + v1->cluster(&cluster); + VertexPtr v2 = make_vertex(graph); + v2->wcpt().point = second_pt; + v2->cluster(&cluster); + // Create Segment using the vertices to derive a path + auto path_points = do_rough_path(cluster, first_pt, second_pt); + auto seg = create_segment_for_cluster(cluster, dv, path_points, 1); + WireCell::Clus::PR::add_segment(graph, seg, v1, v2); + + // perform fitting ... + track_fitter.add_segment(seg); + track_fitter.do_single_tracking(seg, true, true); + const auto& fine_path = track_fitter.get_fine_tracking_path(); + const auto& dQ_vec = track_fitter.get_dQ(); + const auto& dx_vec = track_fitter.get_dx(); + const auto& pu_vec = track_fitter.get_pu(); + const auto& pv_vec = track_fitter.get_pv(); + const auto& pw_vec = track_fitter.get_pw(); + const auto& pt_vec = track_fitter.get_pt(); + const auto& chi2_vec = track_fitter.get_reduced_chi2(); + + if (fine_path.size()>1) { + v1->fit().point = fine_path.front().first; + if (!dQ_vec.empty()) v1->fit().dQ = dQ_vec.front(); + if (!dx_vec.empty()) v1->fit().dx = dx_vec.front(); + if (!pu_vec.empty()) v1->fit().pu = pu_vec.front(); + if (!pv_vec.empty()) v1->fit().pv = pv_vec.front(); + if (!pw_vec.empty()) v1->fit().pw = pw_vec.front(); + if (!pt_vec.empty()) v1->fit().pt = pt_vec.front(); + if (!chi2_vec.empty()) v1->fit().reduced_chi2 = chi2_vec.front(); + + v2->fit().point = fine_path.back().first; + if (!dQ_vec.empty()) v2->fit().dQ = dQ_vec.back(); + if (!dx_vec.empty()) v2->fit().dx = dx_vec.back(); + if (!pu_vec.empty()) v2->fit().pu = pu_vec.back(); + if (!pv_vec.empty()) v2->fit().pv = pv_vec.back(); + if (!pw_vec.empty()) v2->fit().pw = pw_vec.back(); + if (!pt_vec.empty()) v2->fit().pt = pt_vec.back(); + if (!chi2_vec.empty()) v2->fit().reduced_chi2 = chi2_vec.back(); + + // Set fit information for segment + std::vector fits; + for (size_t i = 0; i < fine_path.size(); ++i) { + Fit fit; + fit.point = fine_path[i].first; + if (i < dQ_vec.size()) fit.dQ = dQ_vec[i]; + if (i < dx_vec.size()) fit.dx = dx_vec[i]; + if (i < pu_vec.size()) fit.pu = pu_vec[i]; + if (i < pv_vec.size()) fit.pv = pv_vec[i]; + if (i < pw_vec.size()) fit.pw = pw_vec[i]; + if (i < pt_vec.size()) fit.pt = pt_vec[i]; + if (i < chi2_vec.size()) fit.reduced_chi2 = chi2_vec[i]; + fits.push_back(fit); + } + seg->fits(fits); + }else{ + // Tracking failed, clean up + remove_segment(graph, seg); + remove_vertex(graph, v1); + remove_vertex(graph, v2); + return nullptr; + } + + return seg; } \ No newline at end of file diff --git a/clus/src/TaggerCheckNeutrino.cxx b/clus/src/TaggerCheckNeutrino.cxx index 571bfae8..36017cdf 100644 --- a/clus/src/TaggerCheckNeutrino.cxx +++ b/clus/src/TaggerCheckNeutrino.cxx @@ -1,4 +1,5 @@ #include "WireCellClus/TaggerCheckNeutrino.h" +#include "WireCellClus/NeutrinoPatternBase.h" // pattern recognition ... class TaggerCheckNeutrino; WIRECELL_FACTORY(TaggerCheckNeutrino, TaggerCheckNeutrino, @@ -7,6 +8,7 @@ WIRECELL_FACTORY(TaggerCheckNeutrino, TaggerCheckNeutrino, using namespace WireCell; using namespace WireCell::Clus; using namespace WireCell::Clus::Facade; +using namespace WireCell::Clus::PR; struct edge_base_t { typedef boost::edge_property_tag kind; @@ -63,6 +65,13 @@ void TaggerCheckNeutrino::visit(Ensemble& ensemble) const main_cluster = cluster; } } + + // tempoarary ... + auto pr_graph = std::make_shared(); + WireCell::Clus::PR::PatternAlgorithms pattern_algos; + auto segment = pattern_algos.init_first_segment(*pr_graph, *main_cluster, main_cluster, m_track_fitter, m_dv); + + } void TaggerCheckNeutrino::load_trackfitting_config(const std::string& config_file) From c0b0091235541d4576f6969757ba7d365d4392b8 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sun, 30 Nov 2025 08:06:58 -0800 Subject: [PATCH 032/111] check the code --- clus/src/NeutrinoPatternBase.cxx | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/clus/src/NeutrinoPatternBase.cxx b/clus/src/NeutrinoPatternBase.cxx index 0c09b0df..f73b6336 100644 --- a/clus/src/NeutrinoPatternBase.cxx +++ b/clus/src/NeutrinoPatternBase.cxx @@ -152,19 +152,20 @@ SegmentPtr PatternAlgorithms::init_first_segment(Graph& graph, Facade::Cluster& std::swap(first_pt, second_pt); } } - } else{ + } else if (main_cluster) { // Non-main cluster: start from the point closest to main cluster - auto main_steiner_pc = main_cluster->get_pc("steiner_pc"); // Find closest distances to main cluster's Steiner point cloud auto knn1 = main_cluster->kd_steiner_knn(1, first_pt, "steiner_pc"); auto knn2 = main_cluster->kd_steiner_knn(1, second_pt, "steiner_pc"); - double dis1 = std::sqrt(knn1[0].second); - double dis2 = std::sqrt(knn2[0].second); - - // Start from the point closer to main cluster - if (dis2 < dis1) { - std::swap(first_pt, second_pt); + if (!knn1.empty() && !knn2.empty()) { + double dis1 = std::sqrt(knn1[0].second); + double dis2 = std::sqrt(knn2[0].second); + + // Start from the point closer to main cluster + if (dis2 < dis1) { + std::swap(first_pt, second_pt); + } } } @@ -177,6 +178,14 @@ SegmentPtr PatternAlgorithms::init_first_segment(Graph& graph, Facade::Cluster& v2->cluster(&cluster); // Create Segment using the vertices to derive a path auto path_points = do_rough_path(cluster, first_pt, second_pt); + + // Check if path has enough points (similar to WCPPID check) + if (path_points.size() <= 1) { + remove_vertex(graph, v1); + remove_vertex(graph, v2); + return nullptr; + } + auto seg = create_segment_for_cluster(cluster, dv, path_points, 1); WireCell::Clus::PR::add_segment(graph, seg, v1, v2); From 70d40336f66bfb1f29ee82b4a512a86658e55506 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sun, 30 Nov 2025 08:17:28 -0800 Subject: [PATCH 033/111] catch code --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 4 ++- clus/src/NeutrinoPatternBase.cxx | 34 ++++++++++++++++----- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index c2c5b247..26d77feb 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -13,7 +13,9 @@ namespace WireCell::Clus::PR { // find the shortest path using steiner graph std::vector do_rough_path(const Facade::Cluster& cluster,Facade::geo_point_t& first_point, Facade::geo_point_t& last_point); - + // create a segment given a path SegmentPtr create_segment_for_cluster(WireCell::Clus::Facade::Cluster& cluster, IDetectorVolumes::pointer dv, const std::vector& path_points, int dir); + // create a segment given two vertices, null, if failed + SegmentPtr create_segment_from_vertices(Graph& graph, Facade::Cluster& cluster, VertexPtr v1, VertexPtr v2, IDetectorVolumes::pointer dv); }; } diff --git a/clus/src/NeutrinoPatternBase.cxx b/clus/src/NeutrinoPatternBase.cxx index f73b6336..13974528 100644 --- a/clus/src/NeutrinoPatternBase.cxx +++ b/clus/src/NeutrinoPatternBase.cxx @@ -116,6 +116,21 @@ SegmentPtr PatternAlgorithms::create_segment_for_cluster(WireCell::Clus::Facade: return segment; } +SegmentPtr PatternAlgorithms::create_segment_from_vertices(Graph& graph, Facade::Cluster& cluster, VertexPtr v1, VertexPtr v2, IDetectorVolumes::pointer dv){ + // Create Segment using the vertices to derive a path + auto path_points = do_rough_path(cluster, v1->wcpt().point, v2->wcpt().point); + + // Check if path has enough points (similar to WCPPID check) + if (path_points.size() <= 1) { + return nullptr; + } + + auto seg = create_segment_for_cluster(cluster, dv, path_points, 1); + WireCell::Clus::PR::add_segment(graph, seg, v1, v2); + return seg; +} + + SegmentPtr PatternAlgorithms::init_first_segment(Graph& graph, Facade::Cluster& cluster, Facade::Cluster* main_cluster,TrackFitting& track_fitter, IDetectorVolumes::pointer dv, bool flag_back_search) { @@ -176,18 +191,21 @@ SegmentPtr PatternAlgorithms::init_first_segment(Graph& graph, Facade::Cluster& VertexPtr v2 = make_vertex(graph); v2->wcpt().point = second_pt; v2->cluster(&cluster); - // Create Segment using the vertices to derive a path - auto path_points = do_rough_path(cluster, first_pt, second_pt); - - // Check if path has enough points (similar to WCPPID check) - if (path_points.size() <= 1) { + + auto seg = create_segment_from_vertices(graph, cluster, v1, v2, dv); + if (!seg) { remove_vertex(graph, v1); remove_vertex(graph, v2); return nullptr; } - - auto seg = create_segment_for_cluster(cluster, dv, path_points, 1); - WireCell::Clus::PR::add_segment(graph, seg, v1, v2); + + // // Create Segment using the vertices to derive a path + // auto path_points = do_rough_path(cluster, first_pt, second_pt); + // // Check if path has enough points (similar to WCPPID check) + // if (path_points.size() <= 1) { + // } + // auto seg = create_segment_for_cluster(cluster, dv, path_points, 1); + // WireCell::Clus::PR::add_segment(graph, seg, v1, v2); // perform fitting ... track_fitter.add_segment(seg); From 6ddfa3604f00040866710b1167f9c228dc5c4741 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sun, 30 Nov 2025 09:10:49 -0800 Subject: [PATCH 034/111] add proto_extend_point --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 3 + clus/src/NeutrinoPatternBase.cxx | 105 +++++++++++++++++++- 2 files changed, 107 insertions(+), 1 deletion(-) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 26d77feb..482ab729 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -17,5 +17,8 @@ namespace WireCell::Clus::PR { SegmentPtr create_segment_for_cluster(WireCell::Clus::Facade::Cluster& cluster, IDetectorVolumes::pointer dv, const std::vector& path_points, int dir); // create a segment given two vertices, null, if failed SegmentPtr create_segment_from_vertices(Graph& graph, Facade::Cluster& cluster, VertexPtr v1, VertexPtr v2, IDetectorVolumes::pointer dv); + + std::pair proto_extend_point(const Facade::Cluster& cluster, Facade::geo_point_t& p, Facade::geo_vector_t& dir, Facade::geo_vector_t& dir_other, bool flag_continue); + }; } diff --git a/clus/src/NeutrinoPatternBase.cxx b/clus/src/NeutrinoPatternBase.cxx index 13974528..e776527a 100644 --- a/clus/src/NeutrinoPatternBase.cxx +++ b/clus/src/NeutrinoPatternBase.cxx @@ -262,4 +262,107 @@ SegmentPtr PatternAlgorithms::init_first_segment(Graph& graph, Facade::Cluster& } return seg; -} \ No newline at end of file +} + + +std::pair PatternAlgorithms::proto_extend_point(const Facade::Cluster& cluster, Facade::geo_point_t& p, Facade::geo_vector_t& dir, Facade::geo_vector_t& dir_other, bool flag_continue){ + const double step_dis = 1.0 * units::cm; + + // Get steiner point cloud data + const auto& steiner_pc = cluster.get_pc("steiner_pc"); + const auto& coords = cluster.get_default_scope().coords; + const auto& steiner_x = steiner_pc.get(coords.at(0))->elements(); + const auto& steiner_y = steiner_pc.get(coords.at(1))->elements(); + const auto& steiner_z = steiner_pc.get(coords.at(2))->elements(); + + // Find closest point in steiner point cloud + auto curr_knn_results = cluster.kd_steiner_knn(1, p, "steiner_pc"); + if (curr_knn_results.empty()) { + return std::make_pair(p, -1); // No steiner point found ... + } + + size_t curr_index = curr_knn_results[0].first; + Facade::geo_point_t curr_wcp(steiner_x[curr_index], steiner_y[curr_index], steiner_z[curr_index]); + Facade::geo_point_t next_wcp = curr_wcp; + + // Save starting position and direction + Facade::geo_point_t saved_start_wcp = curr_wcp; + Facade::geo_vector_t saved_dir = dir; + + // Forward search + while(flag_continue){ + flag_continue = false; + + for (int i = 0; i != 3; i++){ + Facade::geo_point_t test_p( + curr_wcp.x() + dir.x() * step_dis * (i + 1), + curr_wcp.y() + dir.y() * step_dis * (i + 1), + curr_wcp.z() + dir.z() * step_dis * (i + 1) + ); + + // Try steiner point cloud first + auto next_knn_steiner = cluster.kd_steiner_knn(1, test_p, "steiner_pc"); + if (!next_knn_steiner.empty()) { + size_t next_index = next_knn_steiner[0].first; + next_wcp = Facade::geo_point_t(steiner_x[next_index], steiner_y[next_index], steiner_z[next_index]); + Facade::geo_vector_t dir2( + next_wcp.x() - curr_wcp.x(), + next_wcp.y() - curr_wcp.y(), + next_wcp.z() - curr_wcp.z() + ); + + double mag2 = dir2.magnitude(); + if (mag2 != 0) { + double angle = std::acos(dir2.dot(dir) / mag2) / 3.1415926 * 180.0; + if (angle < 25.0) { + flag_continue = true; + curr_wcp = next_wcp; + curr_index = next_index; + dir = dir2 + dir * 5.0 * units::cm; // momentum trick + dir = dir / dir.magnitude(); + break; + } + } + } + + // Try regular point cloud + auto closest_result = cluster.get_closest_wcpoint(test_p); + // size_t regular_index = closest_result.first; + next_wcp = closest_result.second; + + Facade::geo_vector_t dir1( + next_wcp.x() - curr_wcp.x(), + next_wcp.y() - curr_wcp.y(), + next_wcp.z() - curr_wcp.z() + ); + + double mag1 = dir1.magnitude(); + if (mag1 != 0) { + double angle = std::acos(dir1.dot(dir) / mag1) / 3.1415926 * 180.0; + if (angle < 17.5) { + flag_continue = true; + curr_wcp = next_wcp; + // For regular point cloud, we need to find it in steiner cloud again + auto updated_knn = cluster.kd_steiner_knn(1, curr_wcp, "steiner_pc"); + if (!updated_knn.empty()) { + curr_index = updated_knn[0].first; + } + dir = dir1 + dir * 5.0 * units::cm; // momentum trick + dir = dir / dir.magnitude(); + break; + } + } + } + } + + // Ensure we return the steiner point cloud position + Facade::geo_point_t test_p(curr_wcp.x(), curr_wcp.y(), curr_wcp.z()); + auto final_knn = cluster.kd_steiner_knn(1, test_p, "steiner_pc"); + if (!final_knn.empty()) { + curr_index = final_knn[0].first; + curr_wcp = Facade::geo_point_t(steiner_x[curr_index], steiner_y[curr_index], steiner_z[curr_index]); + } + + // Return: point, (cloud_type=2 for steiner, point_index) + return std::make_pair(curr_wcp, curr_index); +} From 19ff94b15b5d640e324c9f5f7d51ee2e5fcd3f26 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sun, 30 Nov 2025 16:04:10 -0800 Subject: [PATCH 035/111] implement function proto_break_tracks --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 4 ++ clus/src/NeutrinoPatternBase.cxx | 77 +++++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 482ab729..55bad2ab 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -18,7 +18,11 @@ namespace WireCell::Clus::PR { // create a segment given two vertices, null, if failed SegmentPtr create_segment_from_vertices(Graph& graph, Facade::Cluster& cluster, VertexPtr v1, VertexPtr v2, IDetectorVolumes::pointer dv); + // return the point and its index in the steiner tree as a pair std::pair proto_extend_point(const Facade::Cluster& cluster, Facade::geo_point_t& p, Facade::geo_vector_t& dir, Facade::geo_vector_t& dir_other, bool flag_continue); + + // return Steiner Graph path in wcps_list1 and wcps_list2 + bool proto_break_tracks(const Facade::Cluster& cluster, const Facade::geo_point_t& first_wcp, const Facade::geo_point_t& curr_wcp, const Facade::geo_point_t& last_wcp, std::list& wcps_list1, std::list& wcps_list2, bool flag_pass_check); }; } diff --git a/clus/src/NeutrinoPatternBase.cxx b/clus/src/NeutrinoPatternBase.cxx index e776527a..6418163e 100644 --- a/clus/src/NeutrinoPatternBase.cxx +++ b/clus/src/NeutrinoPatternBase.cxx @@ -366,3 +366,80 @@ std::pair PatternAlgorithms::proto_extend_point(co // Return: point, (cloud_type=2 for steiner, point_index) return std::make_pair(curr_wcp, curr_index); } + +bool PatternAlgorithms::proto_break_tracks(const Facade::Cluster& cluster, const Facade::geo_point_t& first_wcp, const Facade::geo_point_t& curr_wcp, const Facade::geo_point_t& last_wcp, std::list& wcps_list1, std::list& wcps_list2, bool flag_pass_check){ + + // Calculate distances + double dis1 = std::sqrt(std::pow(curr_wcp.x() - first_wcp.x(), 2) + + std::pow(curr_wcp.y() - first_wcp.y(), 2) + + std::pow(curr_wcp.z() - first_wcp.z(), 2)); + double dis2 = std::sqrt(std::pow(curr_wcp.x() - last_wcp.x(), 2) + + std::pow(curr_wcp.y() - last_wcp.y(), 2) + + std::pow(curr_wcp.z() - last_wcp.z(), 2)); + + // Check if distances are sufficient or if we should pass the check + if ((dis1 > 1.0 * units::cm && dis2 > 1.0 * units::cm) || flag_pass_check) { + // Find shortest path from first_wcp to curr_wcp using steiner graph + Facade::geo_point_t first_point = first_wcp; + Facade::geo_point_t curr_point = curr_wcp; + auto path1 = do_rough_path(cluster, first_point, curr_point); + // Convert vector to list + wcps_list1.clear(); + for (const auto& pt : path1) { + wcps_list1.push_back(pt); + } + + // Find shortest path from curr_wcp to last_wcp using steiner graph + Facade::geo_point_t curr_point2 = curr_wcp; + Facade::geo_point_t last_point = last_wcp; + auto path2 = do_rough_path(cluster, curr_point2, last_point); + // Convert vector to list + wcps_list2.clear(); + for (const auto& pt : path2) { + wcps_list2.push_back(pt); + } + + // Remove overlapping points at the junction + // Count how many points overlap from the end of list1 and beginning of list2 + int count = 0; + if (!wcps_list1.empty() && !wcps_list2.empty()) { + // Compare points from the end of list1 with the beginning of list2 + // Use reverse iterator for list1 and forward iterator for list2 + auto it1 = wcps_list1.rbegin(); // Start from the back of list1 + auto it2 = wcps_list2.begin(); // Start from the front of list2 + + while (it1 != wcps_list1.rend() && it2 != wcps_list2.end()) { + // Check if points are the same (within tolerance) + double dx = it1->x() - it2->x(); + double dy = it1->y() - it2->y(); + double dz = it1->z() - it2->z(); + double dist = std::sqrt(dx*dx + dy*dy + dz*dz); + + if (dist < 0.01 * units::cm) { // same point + count++; + ++it1; + ++it2; + } else { + break; // no more overlapping points + } + } + + // Remove overlapping points (keep one copy at the junction) + for (int i = 0; i < count; i++) { + if (i + 1 != count) { // Keep the last overlapping point + if (!wcps_list1.empty()) wcps_list1.pop_back(); + if (!wcps_list2.empty()) wcps_list2.pop_front(); + } + } + } + + // Check if we have valid paths + if (wcps_list1.size() <= 1 || wcps_list2.size() <= 1) { + return false; + } + + return true; + } else { + return false; + } +} From ae3556fa8f8782a015cee3ad07d3d9df2793c059 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sun, 30 Nov 2025 16:54:05 -0800 Subject: [PATCH 036/111] implement the replace_segment_and_vertex function --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 9 ++- clus/src/NeutrinoPatternBase.cxx | 73 ++++++++++++++++++++- 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 55bad2ab..ec104400 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -14,15 +14,20 @@ namespace WireCell::Clus::PR { // find the shortest path using steiner graph std::vector do_rough_path(const Facade::Cluster& cluster,Facade::geo_point_t& first_point, Facade::geo_point_t& last_point); // create a segment given a path - SegmentPtr create_segment_for_cluster(WireCell::Clus::Facade::Cluster& cluster, IDetectorVolumes::pointer dv, const std::vector& path_points, int dir); + SegmentPtr create_segment_for_cluster(WireCell::Clus::Facade::Cluster& cluster, IDetectorVolumes::pointer dv, const std::vector& path_points, int dir = 0); // create a segment given two vertices, null, if failed SegmentPtr create_segment_from_vertices(Graph& graph, Facade::Cluster& cluster, VertexPtr v1, VertexPtr v2, IDetectorVolumes::pointer dv); + // replace a segment and vertex with another segment and vertex, assuming the original vertex only connect to this segment + bool replace_segment_and_vertex(Graph& graph, SegmentPtr& seg, VertexPtr& vtx, std::list& path_point_list, Facade::geo_point_t& break_point, IDetectorVolumes::pointer dv); + // return the point and its index in the steiner tree as a pair std::pair proto_extend_point(const Facade::Cluster& cluster, Facade::geo_point_t& p, Facade::geo_vector_t& dir, Facade::geo_vector_t& dir_other, bool flag_continue); - // return Steiner Graph path in wcps_list1 and wcps_list2 bool proto_break_tracks(const Facade::Cluster& cluster, const Facade::geo_point_t& first_wcp, const Facade::geo_point_t& curr_wcp, const Facade::geo_point_t& last_wcp, std::list& wcps_list1, std::list& wcps_list2, bool flag_pass_check); + // breaking segments ... + bool break_segments(Graph& graph, std::vector& remaining_segments, float dis_cut); + }; } diff --git a/clus/src/NeutrinoPatternBase.cxx b/clus/src/NeutrinoPatternBase.cxx index 6418163e..1a20de60 100644 --- a/clus/src/NeutrinoPatternBase.cxx +++ b/clus/src/NeutrinoPatternBase.cxx @@ -125,7 +125,7 @@ SegmentPtr PatternAlgorithms::create_segment_from_vertices(Graph& graph, Facade: return nullptr; } - auto seg = create_segment_for_cluster(cluster, dv, path_points, 1); + auto seg = create_segment_for_cluster(cluster, dv, path_points); WireCell::Clus::PR::add_segment(graph, seg, v1, v2); return seg; } @@ -204,7 +204,7 @@ SegmentPtr PatternAlgorithms::init_first_segment(Graph& graph, Facade::Cluster& // // Check if path has enough points (similar to WCPPID check) // if (path_points.size() <= 1) { // } - // auto seg = create_segment_for_cluster(cluster, dv, path_points, 1); + // auto seg = create_segment_for_cluster(cluster, dv, path_points); // WireCell::Clus::PR::add_segment(graph, seg, v1, v2); // perform fitting ... @@ -443,3 +443,72 @@ bool PatternAlgorithms::proto_break_tracks(const Facade::Cluster& cluster, const return false; } } + +bool PatternAlgorithms::break_segments(Graph& graph, std::vector& remaining_segments, float dis_cut) { + + return true; +} + + +bool PatternAlgorithms::replace_segment_and_vertex(Graph& graph, SegmentPtr& seg, VertexPtr& vtx, std::list& path_point_list, Facade::geo_point_t& break_point, IDetectorVolumes::pointer dv) { + // Check that the vertex is only connected to one segment + if (!vtx->descriptor_valid()) { + return false; + } + auto vd = vtx->get_descriptor(); + if (boost::degree(vd, graph) != 1) { + return false; // Vertex is connected to more than one segment, cannot replace + } + + // Get the cluster from the old segment + auto cluster = seg->cluster(); + if (!cluster) { + return false; + } + + // Get the other vertex connected to this segment (the one we'll keep) + VertexPtr other_vertex = find_other_vertex(graph, seg, vtx); + if (!other_vertex) { + return false; + } + + // Create new vertex at the break point + VertexPtr new_vtx = make_vertex(graph); + new_vtx->wcpt().point = break_point; + new_vtx->cluster(cluster); + + // Convert list to vector for create_segment_for_cluster + std::vector path_points; + for (const auto& pt : path_point_list) { + path_points.push_back(pt); + } + + // Check if path has enough points + if (path_points.size() <= 1) { + remove_vertex(graph, new_vtx); + return false; + } + + // Create new segment with the path points + SegmentPtr new_seg = create_segment_for_cluster(*cluster, dv, path_points, seg->dirsign()); + if (!new_seg) { + remove_vertex(graph, new_vtx); + return false; + } + + // Remove the old segment (this will disconnect it from the graph) + remove_segment(graph, seg); + // Remove the old vertex + remove_vertex(graph, vtx); + + // Add the new segment connecting other_vertex and new_vtx + add_segment(graph, new_seg, other_vertex, new_vtx); + + // Update the output parameters + seg = new_seg; + vtx = new_vtx; + + return true; +} + + From ab4fcba1a74abe4e197ee7d260eda3a8ac56c9d0 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sun, 30 Nov 2025 17:07:09 -0800 Subject: [PATCH 037/111] break_segment_into_two --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 1 + clus/src/NeutrinoPatternBase.cxx | 61 ++++++++++++++++++++- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index ec104400..8d247456 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -19,6 +19,7 @@ namespace WireCell::Clus::PR { SegmentPtr create_segment_from_vertices(Graph& graph, Facade::Cluster& cluster, VertexPtr v1, VertexPtr v2, IDetectorVolumes::pointer dv); // replace a segment and vertex with another segment and vertex, assuming the original vertex only connect to this segment bool replace_segment_and_vertex(Graph& graph, SegmentPtr& seg, VertexPtr& vtx, std::list& path_point_list, Facade::geo_point_t& break_point, IDetectorVolumes::pointer dv); + bool break_segment_into_two(Graph& graph, VertexPtr vtx1, SegmentPtr seg, VertexPtr vtx2, std::list& path_point_list1, Facade::geo_point_t& break_point, std::list& path_point_list2, IDetectorVolumes::pointer dv); // return the point and its index in the steiner tree as a pair diff --git a/clus/src/NeutrinoPatternBase.cxx b/clus/src/NeutrinoPatternBase.cxx index 1a20de60..216291e4 100644 --- a/clus/src/NeutrinoPatternBase.cxx +++ b/clus/src/NeutrinoPatternBase.cxx @@ -511,4 +511,63 @@ bool PatternAlgorithms::replace_segment_and_vertex(Graph& graph, SegmentPtr& seg return true; } - + bool PatternAlgorithms::break_segment_into_two(Graph& graph, VertexPtr vtx1, SegmentPtr seg, VertexPtr vtx2, std::list& path_point_list1, Facade::geo_point_t& break_point, std::list& path_point_list2, IDetectorVolumes::pointer dv){ + // Get the cluster from the old segment + auto cluster = seg->cluster(); + if (!cluster) { + return false; + } + + // Verify that vtx1 and vtx2 are the endpoints of seg + auto [v1, v2] = find_vertices(graph, seg); + if ((v1 != vtx1 || v2 != vtx2) && (v1 != vtx2 || v2 != vtx1)) { + return false; // The provided vertices don't match the segment endpoints + } + + // Create new vertex at the break point + VertexPtr new_vtx = make_vertex(graph); + new_vtx->wcpt().point = break_point; + new_vtx->cluster(cluster); + + // Convert lists to vectors for create_segment_for_cluster + std::vector path_points1; + for (const auto& pt : path_point_list1) { + path_points1.push_back(pt); + } + + std::vector path_points2; + for (const auto& pt : path_point_list2) { + path_points2.push_back(pt); + } + + // Check if paths have enough points + if (path_points1.size() <= 1 || path_points2.size() <= 1) { + remove_vertex(graph, new_vtx); + return false; + } + + // Create first new segment with path_points1 + SegmentPtr new_seg1 = create_segment_for_cluster(*cluster, dv, path_points1, seg->dirsign()); + if (!new_seg1) { + remove_vertex(graph, new_vtx); + return false; + } + + // Create second new segment with path_points2 + SegmentPtr new_seg2 = create_segment_for_cluster(*cluster, dv, path_points2, seg->dirsign()); + if (!new_seg2) { + remove_vertex(graph, new_vtx); + return false; + } + + // Remove the old segment + remove_segment(graph, seg); + + // Add the first new segment connecting vtx1 and new_vtx + add_segment(graph, new_seg1, vtx1, new_vtx); + + // Add the second new segment connecting new_vtx and vtx2 + add_segment(graph, new_seg2, new_vtx, vtx2); + + return true; + } From a0572c3b09a0318953245a3e23d48d92046baaf2 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sun, 30 Nov 2025 17:48:47 -0800 Subject: [PATCH 038/111] implement break_segments function --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 6 +- clus/src/NeutrinoPatternBase.cxx | 174 +++++++++++++++++++- clus/src/TaggerCheckNeutrino.cxx | 1 + clus/src/TrackFitting.cxx | 2 + 4 files changed, 172 insertions(+), 11 deletions(-) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 8d247456..d982a183 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -5,8 +5,8 @@ namespace WireCell::Clus::PR { class PatternAlgorithms{ public: - std::set find_vertices(Graph& graph, const Facade::Cluster& cluster); - std::set find_segments(Graph& graph, const Facade::Cluster& cluster); + std::set find_cluster_vertices(Graph& graph, const Facade::Cluster& cluster); + std::set find_cluster_segments(Graph& graph, const Facade::Cluster& cluster); bool clean_up_graph(Graph& graph, const Facade::Cluster& cluster); SegmentPtr init_first_segment(Graph& graph, Facade::Cluster& cluster, Facade::Cluster* main_cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, bool flag_back_search = true); @@ -27,7 +27,7 @@ namespace WireCell::Clus::PR { // return Steiner Graph path in wcps_list1 and wcps_list2 bool proto_break_tracks(const Facade::Cluster& cluster, const Facade::geo_point_t& first_wcp, const Facade::geo_point_t& curr_wcp, const Facade::geo_point_t& last_wcp, std::list& wcps_list1, std::list& wcps_list2, bool flag_pass_check); // breaking segments ... - bool break_segments(Graph& graph, std::vector& remaining_segments, float dis_cut); + bool break_segments(Graph& graph, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, std::vector& remaining_segments, float dis_cut); }; diff --git a/clus/src/NeutrinoPatternBase.cxx b/clus/src/NeutrinoPatternBase.cxx index 216291e4..5b24dc1d 100644 --- a/clus/src/NeutrinoPatternBase.cxx +++ b/clus/src/NeutrinoPatternBase.cxx @@ -4,7 +4,7 @@ using namespace WireCell::Clus::PR; using namespace WireCell::Clus; -std::set PatternAlgorithms::find_vertices(Graph& graph, const Facade::Cluster& cluster) +std::set PatternAlgorithms::find_cluster_vertices(Graph& graph, const Facade::Cluster& cluster) { std::set result; @@ -22,7 +22,7 @@ std::set PatternAlgorithms::find_vertices(Graph& graph, const Facade: return result; } -std::set PatternAlgorithms::find_segments(Graph& graph, const Facade::Cluster& cluster) +std::set PatternAlgorithms::find_cluster_segments(Graph& graph, const Facade::Cluster& cluster) { std::set result; @@ -45,7 +45,7 @@ bool PatternAlgorithms::clean_up_graph(Graph& graph, const Facade::Cluster& clus bool modified = false; // First, find and remove all segments associated with this cluster - std::set segments_to_remove = find_segments(graph, cluster); + std::set segments_to_remove = find_cluster_segments(graph, cluster); for (auto seg : segments_to_remove) { if (remove_segment(graph, seg)) { modified = true; @@ -55,7 +55,7 @@ bool PatternAlgorithms::clean_up_graph(Graph& graph, const Facade::Cluster& clus // Then, find and remove all vertices associated with this cluster // Note: vertices that are still connected to other segments won't be removed // until their segments are removed first - std::set vertices_to_remove = find_vertices(graph, cluster); + std::set vertices_to_remove = find_cluster_vertices(graph, cluster); for (auto vtx : vertices_to_remove) { if (remove_vertex(graph, vtx)) { modified = true; @@ -444,10 +444,7 @@ bool PatternAlgorithms::proto_break_tracks(const Facade::Cluster& cluster, const } } -bool PatternAlgorithms::break_segments(Graph& graph, std::vector& remaining_segments, float dis_cut) { - - return true; -} + bool PatternAlgorithms::replace_segment_and_vertex(Graph& graph, SegmentPtr& seg, VertexPtr& vtx, std::list& path_point_list, Facade::geo_point_t& break_point, IDetectorVolumes::pointer dv) { @@ -571,3 +568,164 @@ bool PatternAlgorithms::replace_segment_and_vertex(Graph& graph, SegmentPtr& seg return true; } + + bool PatternAlgorithms::break_segments(Graph& graph, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, std::vector& remaining_segments, float dis_cut) { + bool flag_modified = false; + int count = 0; + std::set saved_break_wcp_indices; + + while(!remaining_segments.empty() && count < 2) { + SegmentPtr curr_sg = remaining_segments.back(); + auto cluster = curr_sg->cluster(); + remaining_segments.pop_back(); + + // Get the two vertices of this segment + auto [start_v, end_v] = find_vertices(graph, curr_sg); + if (!start_v || !end_v) { + continue; + } + + // Check if vertices match the segment endpoints + const auto& wcpts = curr_sg->wcpts(); + if (wcpts.size() < 2) continue; + + auto front_pt = wcpts.front().point; + auto back_pt = wcpts.back().point; + + // Determine which vertex is start and which is end based on point positions + double dis_sv_front = ray_length(Ray{start_v->wcpt().point, front_pt}); + double dis_sv_back = ray_length(Ray{start_v->wcpt().point, back_pt}); + + if (dis_sv_front > dis_sv_back) { + std::swap(start_v, end_v); + } + + // Initialize the start test point + Facade::geo_point_t break_wcp = start_v->wcpt().point; + const auto& point_vec = curr_sg->wcpts(); + Facade::geo_point_t test_start_p = point_vec.front().point; + + if (dis_cut > 0) { + for (size_t i = 0; i < point_vec.size(); ++i) { + double dis = ray_length(Ray{point_vec[i].point, point_vec.front().point}); + if (dis > dis_cut) { + test_start_p = point_vec[i].point; + break; + } + } + } + + // Search for kinks and extend the break point + while(ray_length(Ray{start_v->wcpt().point, break_wcp}) <= 1.0 * units::cm && + ray_length(Ray{end_v->wcpt().point, break_wcp}) > 1.0 * units::cm) { + + auto kink_tuple = segment_search_kink(curr_sg, test_start_p, "fit"); + auto& [kink_point, dir1, dir2, flag_continue] = kink_tuple; + + if (dir1.magnitude() != 0) { + // Find the extreme point + Facade::geo_vector_t dir1_geo(dir1.x(), dir1.y(), dir1.z()); + Facade::geo_vector_t dir2_geo(dir2.x(), dir2.y(), dir2.z()); + Facade::geo_point_t kink_geo(kink_point.x(), kink_point.y(), kink_point.z()); + + auto [break_pt, break_idx] = proto_extend_point(*cluster, kink_geo, dir1_geo, dir2_geo, flag_continue); + break_wcp = break_pt; + + // Check if we've seen this break point before + if (saved_break_wcp_indices.find(break_idx) != saved_break_wcp_indices.end()) { + test_start_p = kink_geo; + kink_tuple = segment_search_kink(curr_sg, test_start_p, "fit"); + auto& [kink_point2, dir1_2, dir2_2, flag_continue2] = kink_tuple; + Facade::geo_vector_t dir1_geo2(dir1_2.x(), dir1_2.y(), dir1_2.z()); + Facade::geo_vector_t dir2_geo2(dir2_2.x(), dir2_2.y(), dir2_2.z()); + Facade::geo_point_t kink_geo2(kink_point2.x(), kink_point2.y(), kink_point2.z()); + auto [break_pt2, break_idx2] = proto_extend_point(*cluster, kink_geo2, dir1_geo2, dir2_geo2, flag_continue2); + break_wcp = break_pt2; + break_idx = break_idx2; + } else { + saved_break_wcp_indices.insert(break_idx); + } + + if (ray_length(Ray{start_v->wcpt().point, break_wcp}) <= 1.0 * units::cm && + ray_length(Ray{end_v->wcpt().point, break_wcp}) > 1.0 * units::cm) { + test_start_p = kink_geo; + } + } else { + break; + } + } + + // Check if we should break the segment + if (ray_length(Ray{start_v->wcpt().point, break_wcp}) > 1.0 * units::cm) { + std::list wcps_list1; + std::list wcps_list2; + + bool flag_break; + bool flag_pass_check = false; + + // Check if end vertex is close to break point and has only one connection + if (ray_length(Ray{end_v->wcpt().point, break_wcp}) < 1.0 * units::cm) { + auto vd = end_v->get_descriptor(); + if (boost::degree(vd, graph) == 1) { + flag_pass_check = true; + } + } + + flag_break = proto_break_tracks(*cluster, start_v->wcpt().point, break_wcp, + end_v->wcpt().point, wcps_list1, wcps_list2, flag_pass_check); + + if (flag_break) { + // Check geometry constraints + Facade::geo_vector_t tv1 = end_v->wcpt().point - start_v->wcpt().point; + Facade::geo_vector_t tv2 = end_v->wcpt().point - break_wcp; + + double min_dis = 1e9; + for (const auto& wcp : wcps_list1) { + double dis = ray_length(Ray{wcp, end_v->wcpt().point}); + if (dis < min_dis) min_dis = dis; + } + + double angle = std::acos(tv1.dot(tv2) / (tv1.magnitude() * tv2.magnitude())) / 3.1415926 * 180.0; + + // Check if we should replace end vertex instead of breaking + if (min_dis / units::cm < 1.5 && angle > 120) { + auto vd = end_v->get_descriptor(); + if (boost::degree(vd, graph) == 1) { + // Replace segment and end vertex + SegmentPtr new_seg = curr_sg; + VertexPtr new_vtx = end_v; + if (replace_segment_and_vertex(graph, new_seg, new_vtx, wcps_list1, break_wcp, dv)) { + flag_modified = true; + // Perform tracking + // track_fitter.add_graph(&graph); added already + track_fitter.do_multi_tracking(true, true, false); + } + } + } else { + // Break segment into two + if (break_segment_into_two(graph, start_v, curr_sg, end_v, wcps_list1, break_wcp, wcps_list2, dv)) { + flag_modified = true; + // Perform tracking + // track_fitter.add_graph(&graph); added already + track_fitter.do_multi_tracking(true, true, false); + + // Find the new segment connecting to end_v and add it back to remaining_segments + auto [new_vtx, new_end_v] = find_vertices(graph, curr_sg); + // The new segment created from wcps_list2 connects new_vtx to end_v + // We need to find it + auto erange = boost::out_edges(end_v->get_descriptor(), graph); + for (auto eit = erange.first; eit != erange.second; ++eit) { + SegmentPtr seg = graph[*eit].segment; + if (seg && seg->cluster() == curr_sg->cluster()) { + remaining_segments.push_back(seg); + break; + } + } + } + } + } + } + } + + return flag_modified; +} diff --git a/clus/src/TaggerCheckNeutrino.cxx b/clus/src/TaggerCheckNeutrino.cxx index 36017cdf..11275bb6 100644 --- a/clus/src/TaggerCheckNeutrino.cxx +++ b/clus/src/TaggerCheckNeutrino.cxx @@ -70,6 +70,7 @@ void TaggerCheckNeutrino::visit(Ensemble& ensemble) const auto pr_graph = std::make_shared(); WireCell::Clus::PR::PatternAlgorithms pattern_algos; auto segment = pattern_algos.init_first_segment(*pr_graph, *main_cluster, main_cluster, m_track_fitter, m_dv); + m_track_fitter.add_graph(pr_graph); } diff --git a/clus/src/TrackFitting.cxx b/clus/src/TrackFitting.cxx index e5dc2e59..6ebef7ac 100644 --- a/clus/src/TrackFitting.cxx +++ b/clus/src/TrackFitting.cxx @@ -229,6 +229,8 @@ void TrackFitting::clear_segments(){ } void TrackFitting::add_graph(std::shared_ptr graph){ + if (m_graph == graph) return; + m_graph = graph; if (!m_graph){ From 8573112901455d1f4c7639a7938bf95d57abcda3 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sun, 30 Nov 2025 18:32:04 -0800 Subject: [PATCH 039/111] update code --- clus/src/NeutrinoPatternBase.cxx | 57 ++++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 10 deletions(-) diff --git a/clus/src/NeutrinoPatternBase.cxx b/clus/src/NeutrinoPatternBase.cxx index 5b24dc1d..0da4cc5c 100644 --- a/clus/src/NeutrinoPatternBase.cxx +++ b/clus/src/NeutrinoPatternBase.cxx @@ -444,18 +444,53 @@ bool PatternAlgorithms::proto_break_tracks(const Facade::Cluster& cluster, const } } +bool PatternAlgorithms::replace_segment_and_vertex(Graph& graph, SegmentPtr& seg, VertexPtr old_vertex, VertexPtr new_vertex, IDetectorVolumes::pointer dv){ + // Get the cluster from the old segment + auto cluster = seg->cluster(); + if (!cluster) { + return false; + } + + // Get the other vertex connected to this segment (the one we'll keep) + VertexPtr other_vertex = find_other_vertex(graph, seg, old_vertex); + if (!other_vertex) { + return false; + } + + // Create new segment with the path points + SegmentPtr new_seg = create_segment_from_vertices(graph, *cluster, other_vertex, new_vertex, dv); + if (!new_seg) { + return false; + } + + // Remove the old segment (this will disconnect it from the graph) + remove_segment(graph, seg); + + // Remove the old vertex if it no longer has any connected segments + if (old_vertex->descriptor_valid()) { + auto vd = old_vertex->get_descriptor(); + if (boost::degree(vd, graph) == 0) { + remove_vertex(graph, old_vertex); + } + } + + // Update the output parameter + seg = new_seg; + + return true; +} bool PatternAlgorithms::replace_segment_and_vertex(Graph& graph, SegmentPtr& seg, VertexPtr& vtx, std::list& path_point_list, Facade::geo_point_t& break_point, IDetectorVolumes::pointer dv) { - // Check that the vertex is only connected to one segment - if (!vtx->descriptor_valid()) { - return false; - } - auto vd = vtx->get_descriptor(); - if (boost::degree(vd, graph) != 1) { - return false; // Vertex is connected to more than one segment, cannot replace - } + // // Check that the vertex is only connected to one segment + // if (!vtx->descriptor_valid()) { + // return false; + // } + // auto vd = vtx->get_descriptor(); + // if (boost::degree(vd, graph) != 1) { + // return false; // Vertex is connected to more than one segment, cannot replace + // } // Get the cluster from the old segment auto cluster = seg->cluster(); @@ -495,8 +530,10 @@ bool PatternAlgorithms::replace_segment_and_vertex(Graph& graph, SegmentPtr& seg // Remove the old segment (this will disconnect it from the graph) remove_segment(graph, seg); - // Remove the old vertex - remove_vertex(graph, vtx); + + // Remove the old vertex, if it no longer has any connected segments + auto vd = vtx->get_descriptor(); + if (boost::degree(vd, graph) == 0) remove_vertex(graph, vtx); // Add the new segment connecting other_vertex and new_vtx add_segment(graph, new_seg, other_vertex, new_vtx); From 067122e25065285b1180a43e948af7242a3d787a Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sun, 30 Nov 2025 18:36:28 -0800 Subject: [PATCH 040/111] merge two segments into one --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 4 ++- clus/src/NeutrinoPatternBase.cxx | 35 +++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index d982a183..ebf6cd1a 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -19,6 +19,7 @@ namespace WireCell::Clus::PR { SegmentPtr create_segment_from_vertices(Graph& graph, Facade::Cluster& cluster, VertexPtr v1, VertexPtr v2, IDetectorVolumes::pointer dv); // replace a segment and vertex with another segment and vertex, assuming the original vertex only connect to this segment bool replace_segment_and_vertex(Graph& graph, SegmentPtr& seg, VertexPtr& vtx, std::list& path_point_list, Facade::geo_point_t& break_point, IDetectorVolumes::pointer dv); + bool replace_segment_and_vertex(Graph& graph, SegmentPtr& seg, VertexPtr old_vertex, VertexPtr new_vertex, IDetectorVolumes::pointer dv); bool break_segment_into_two(Graph& graph, VertexPtr vtx1, SegmentPtr seg, VertexPtr vtx2, std::list& path_point_list1, Facade::geo_point_t& break_point, std::list& path_point_list2, IDetectorVolumes::pointer dv); @@ -28,7 +29,8 @@ namespace WireCell::Clus::PR { bool proto_break_tracks(const Facade::Cluster& cluster, const Facade::geo_point_t& first_wcp, const Facade::geo_point_t& curr_wcp, const Facade::geo_point_t& last_wcp, std::list& wcps_list1, std::list& wcps_list2, bool flag_pass_check); // breaking segments ... bool break_segments(Graph& graph, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, std::vector& remaining_segments, float dis_cut); - + // merge two segments to one + bool merge_two_segments_into_one(Graph& graph, SegmentPtr& seg1, VertexPtr& vtx, SegmentPtr& seg2, IDetectorVolumes::pointer dv); }; } diff --git a/clus/src/NeutrinoPatternBase.cxx b/clus/src/NeutrinoPatternBase.cxx index 0da4cc5c..b35ac29a 100644 --- a/clus/src/NeutrinoPatternBase.cxx +++ b/clus/src/NeutrinoPatternBase.cxx @@ -766,3 +766,38 @@ bool PatternAlgorithms::replace_segment_and_vertex(Graph& graph, SegmentPtr& seg return flag_modified; } + + +bool PatternAlgorithms::merge_two_segments_into_one(Graph& graph, SegmentPtr& seg1, VertexPtr& vtx, SegmentPtr& seg2, IDetectorVolumes::pointer dv){ + // Get cluster from seg1 (should be same as seg2) + auto cluster = seg1->cluster(); + if (!cluster || cluster != seg2->cluster()) { + return false; + } + + // Get the other vertices (not vtx) from seg1 and seg2 + VertexPtr vtx1 = find_other_vertex(graph, seg1, vtx); + VertexPtr vtx2 = find_other_vertex(graph, seg2, vtx); + + if (!vtx1 || !vtx2) { + return false; + } + + // Create new segment from vtx1 to vtx2 + SegmentPtr new_seg = create_segment_from_vertices(graph, *cluster, vtx1, vtx2, dv); + if (!new_seg) { + return false; + } + + // Delete old segments + remove_segment(graph, seg1); + remove_segment(graph, seg2); + + // Delete the middle vertex + remove_vertex(graph, vtx); + + // Update output parameter + seg1 = new_seg; + + return true; +} From 1b71a1e8de41164a46dc96e5540724368840d67d Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sun, 30 Nov 2025 18:45:36 -0800 Subject: [PATCH 041/111] add merge vertices --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 2 + clus/src/NeutrinoPatternBase.cxx | 77 +++++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index ebf6cd1a..a14686fa 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -31,6 +31,8 @@ namespace WireCell::Clus::PR { bool break_segments(Graph& graph, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, std::vector& remaining_segments, float dis_cut); // merge two segments to one bool merge_two_segments_into_one(Graph& graph, SegmentPtr& seg1, VertexPtr& vtx, SegmentPtr& seg2, IDetectorVolumes::pointer dv); + // merge vertex into another + bool merge_vertex_into_another(Graph& graph, VertexPtr& vtx_from, VertexPtr& vtx_to, IDetectorVolumes::pointer dv); }; } diff --git a/clus/src/NeutrinoPatternBase.cxx b/clus/src/NeutrinoPatternBase.cxx index b35ac29a..6fe4c8a5 100644 --- a/clus/src/NeutrinoPatternBase.cxx +++ b/clus/src/NeutrinoPatternBase.cxx @@ -801,3 +801,80 @@ bool PatternAlgorithms::merge_two_segments_into_one(Graph& graph, SegmentPtr& se return true; } + +bool PatternAlgorithms::merge_vertex_into_another(Graph& graph, VertexPtr& vtx_from, VertexPtr& vtx_to, IDetectorVolumes::pointer dv){ + if (!vtx_from || !vtx_to) { + return false; + } + + // Step 1: Check if there's a segment between vtx_from and vtx_to, and delete it + SegmentPtr seg_between = find_segment(graph, vtx_from, vtx_to); + if (seg_between) { + remove_segment(graph, seg_between); + } + + // Step 2 & 3: Check if vertices are at the same position + double distance = ray_length(Ray{vtx_from->wcpt().point, vtx_to->wcpt().point}); + bool same_position = (distance < 0.1 * units::cm); + + // Get all segments connected to vtx_from + if (!vtx_from->descriptor_valid()) { + return false; + } + + auto vd_from = vtx_from->get_descriptor(); + std::vector> segments_to_reconnect; + + // Collect all segments and their other endpoints + auto edge_range = boost::out_edges(vd_from, graph); + for (auto eit = edge_range.first; eit != edge_range.second; ++eit) { + SegmentPtr seg = graph[*eit].segment; + if (seg) { + VertexPtr other_vtx = find_other_vertex(graph, seg, vtx_from); + if (other_vtx) { + segments_to_reconnect.push_back(std::make_pair(seg, other_vtx)); + } + } + } + + // Process each segment + for (auto& [old_seg, other_vtx] : segments_to_reconnect) { + auto cluster = old_seg->cluster(); + if (!cluster) continue; + + SegmentPtr new_seg; + + if (same_position) { + // Step 2: Vertices are at same position - just reconnect the segment + // Extract the path from the old segment + const auto& wcpts = old_seg->wcpts(); + std::vector path_points; + for (const auto& wcp : wcpts) { + path_points.push_back(wcp.point); + } + + if (path_points.size() > 1) { + // Create new segment with same path + new_seg = create_segment_for_cluster(*cluster, dv, path_points, old_seg->dirsign()); + if (new_seg) { + // Remove old segment + remove_segment(graph, old_seg); + // Add new segment connecting vtx_to and other_vtx + add_segment(graph, new_seg, vtx_to, other_vtx); + } + } + } else { + // Step 3: Vertices are not at same position - recalculate the path + // Remove old segment first + remove_segment(graph, old_seg); + // Create new segment from vtx_to to other_vtx + new_seg = create_segment_from_vertices(graph, *cluster, vtx_to, other_vtx, dv); + // create_segment_from_vertices already adds the segment to the graph + } + } + + // Step 4: Delete vtx_from + remove_vertex(graph, vtx_from); + + return true; +} \ No newline at end of file From 7ceced7c8c6d41944c77f72300a31d59d1a55a4f Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Fri, 5 Dec 2025 07:25:41 -0800 Subject: [PATCH 042/111] implement two examine_structure functions --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 3 + .../WireCellClus/NeutrinoStructureExaminer.h | 0 clus/src/NeutrinoStructureExaminer.cxx | 299 ++++++++++++++++++ 3 files changed, 302 insertions(+) delete mode 100644 clus/inc/WireCellClus/NeutrinoStructureExaminer.h diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index a14686fa..cebdbcb4 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -34,5 +34,8 @@ namespace WireCell::Clus::PR { // merge vertex into another bool merge_vertex_into_another(Graph& graph, VertexPtr& vtx_from, VertexPtr& vtx_to, IDetectorVolumes::pointer dv); + // Structure examination + bool examine_structure_1(Graph& graph, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); + bool examine_structure_2(Graph& graph, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); }; } diff --git a/clus/inc/WireCellClus/NeutrinoStructureExaminer.h b/clus/inc/WireCellClus/NeutrinoStructureExaminer.h deleted file mode 100644 index e69de29b..00000000 diff --git a/clus/src/NeutrinoStructureExaminer.cxx b/clus/src/NeutrinoStructureExaminer.cxx index e69de29b..e83a9973 100644 --- a/clus/src/NeutrinoStructureExaminer.cxx +++ b/clus/src/NeutrinoStructureExaminer.cxx @@ -0,0 +1,299 @@ +#include "WireCellClus/NeutrinoPatternBase.h" +#include "WireCellClus/PRSegmentFunctions.h" + +using namespace WireCell::Clus::PR; +using namespace WireCell::Clus; + +bool PatternAlgorithms::examine_structure_1(Graph& graph, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv){ + // Look at each segment, if the more straight one is better, change it + bool flag_update = false; + + // Get transform and grouping from track_fitter + const auto transform = track_fitter.get_pc_transforms()->pc_transform( + cluster.get_scope_transform(cluster.get_default_scope())); + double cluster_t0 = cluster.get_cluster_t0(); + auto grouping = cluster.grouping(); + + if (!transform || !grouping) { + return false; + } + + // Iterate through all edges (segments) in the graph + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr sg = graph[*eit].segment; + + // Skip if segment doesn't belong to this cluster + if (!sg || sg->cluster() != &cluster) continue; + + // Get segment properties + double length = segment_track_length(sg); + double medium_dQ_dx = segment_median_dQ_dx(sg) / (43e3/units::cm); + + // Check if segment is short enough and has reasonable dQ/dx + if (length < 5*units::cm || (length < 8*units::cm && medium_dQ_dx > 1.5)) { + // Get the two vertices of this segment + auto [vtx1, vtx2] = find_vertices(graph, sg); + if (!vtx1 || !vtx2) continue; + + const auto& wcpts = sg->wcpts(); + if (wcpts.size() < 2) continue; + + // Get start and end points + Facade::geo_point_t start_p = wcpts.front().point; + Facade::geo_point_t end_p = wcpts.back().point; + + // Check the track by testing points along a straight line + double step_size = 0.6 * units::cm; + double distance = std::sqrt(std::pow(start_p.x() - end_p.x(), 2) + + std::pow(start_p.y() - end_p.y(), 2) + + std::pow(start_p.z() - end_p.z(), 2)); + int ncount = std::round(distance / step_size); + + std::vector new_pts; + bool flag_replace = true; + int n_bad = 0; + + // Test points along the straight line + for (int i = 1; i < ncount; i++) { + Facade::geo_point_t test_p( + start_p.x() + (end_p.x() - start_p.x()) / ncount * i, + start_p.y() + (end_p.y() - start_p.y()) / ncount * i, + start_p.z() + (end_p.z() - start_p.z()) / ncount * i + ); + new_pts.push_back(test_p); + + // Check if this point is good + auto test_wpid = dv->contained_by(test_p); + if (test_wpid.face() != -1 && test_wpid.apa() != -1) { + auto temp_p_raw = transform->backward(test_p, cluster_t0, test_wpid.face(), test_wpid.apa()); + if (!grouping->is_good_point(temp_p_raw, test_wpid.apa(), test_wpid.face(), 0.2*units::cm, 0, 0)) { + n_bad++; + } + } + + if (n_bad > 1) { + flag_replace = false; + break; + } + } + + // If the straight line is better, replace the segment path + if (flag_replace) { + // Get steiner point cloud + const auto& steiner_pc = cluster.get_pc("steiner_pc"); + const auto& coords = cluster.get_default_scope().coords; + const auto& x_coords = steiner_pc.get(coords.at(0))->elements(); + const auto& y_coords = steiner_pc.get(coords.at(1))->elements(); + const auto& z_coords = steiner_pc.get(coords.at(2))->elements(); + + // Build new WCPoint list with start point + std::vector new_wcpts; + WCPoint start_wcp = wcpts.front(); + WCPoint end_wcp = wcpts.back(); + + new_wcpts.push_back(start_wcp); + + // Distance threshold for considering points as "same" (0.01 cm) + const double distance_threshold = 0.01 * units::cm; + + // Add intermediate points from steiner cloud + for (size_t i = 0; i < new_pts.size(); i++) { + auto knn_results = cluster.kd_steiner_knn(1, new_pts[i], "steiner_pc"); + if (!knn_results.empty()) { + size_t idx = knn_results[0].first; + WCPoint wcp; + wcp.point = Facade::geo_point_t(x_coords[idx], y_coords[idx], z_coords[idx]); + + // Only add if different from last point (using distance comparison) + double dist_to_last = ray_length(Ray{wcp.point, new_wcpts.back().point}); + if (dist_to_last > distance_threshold) { + new_wcpts.push_back(wcp); + } + + // Stop if we reached the end point (using distance comparison) + double dist_to_end = ray_length(Ray{wcp.point, end_wcp.point}); + if (dist_to_end < distance_threshold) break; + } + } + + // Add end point if not already added (using distance comparison) + double dist_last_to_end = ray_length(Ray{new_wcpts.back().point, end_wcp.point}); + if (dist_last_to_end > distance_threshold) { + new_wcpts.push_back(end_wcp); + } + + // Update the segment with new points + sg->wcpts(new_wcpts); + + flag_update = true; + std::cout << "Cluster: " << cluster.ident() << " replace Track Content with Straight Line for segment with length " << length/units::cm << " cm" << std::endl; + } + } + } + + return flag_update; +} + + +bool PatternAlgorithms::examine_structure_2(Graph& graph, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv){ + bool flag_update = false; + + // Get transform and grouping from track_fitter + const auto transform = track_fitter.get_pc_transforms()->pc_transform( + cluster.get_scope_transform(cluster.get_default_scope())); + double cluster_t0 = cluster.get_cluster_t0(); + auto grouping = cluster.grouping(); + + if (!transform || !grouping) { + return false; + } + + bool flag_continue = true; + while (flag_continue) { + flag_continue = false; + + // Iterate through all vertices in the graph + auto [vbegin, vend] = boost::vertices(graph); + for (auto vit = vbegin; vit != vend; ++vit) { + VertexPtr vtx = graph[*vit].vertex; + + // Skip if vertex doesn't belong to this cluster + if (!vtx || vtx->cluster() != &cluster) continue; + + // Check if this vertex has exactly 2 connected segments + auto vd = vtx->get_descriptor(); + if (boost::degree(vd, graph) != 2) continue; + + // Get the two segments connected to this vertex + auto edge_range = boost::out_edges(vd, graph); + auto eit = edge_range.first; + SegmentPtr sg1 = graph[*eit].segment; + ++eit; + SegmentPtr sg2 = graph[*eit].segment; + + if (!sg1 || !sg2) continue; + + // double length1 = segment_track_length(sg1); + // double length2 = segment_track_length(sg2); + + // Get the other vertices (endpoints) + VertexPtr vtx1 = find_other_vertex(graph, sg1, vtx); + VertexPtr vtx2 = find_other_vertex(graph, sg2, vtx); + + if (!vtx1 || !vtx2) continue; + + // Use fitted points if available, otherwise use wcpt points + Facade::geo_point_t start_p = vtx1->fit().valid() ? vtx1->fit().point : vtx1->wcpt().point; + Facade::geo_point_t end_p = vtx2->fit().valid() ? vtx2->fit().point : vtx2->wcpt().point; + + // Test points along a straight line between the two endpoints + double step_size = 0.6 * units::cm; + double distance = std::sqrt(std::pow(start_p.x() - end_p.x(), 2) + + std::pow(start_p.y() - end_p.y(), 2) + + std::pow(start_p.z() - end_p.z(), 2)); + int ncount = std::round(distance / step_size); + + std::vector new_pts; + bool flag_replace = true; + int n_bad = 0; + + // Test points along the straight line + for (int i = 1; i < ncount; i++) { + Facade::geo_point_t test_p( + start_p.x() + (end_p.x() - start_p.x()) / ncount * i, + start_p.y() + (end_p.y() - start_p.y()) / ncount * i, + start_p.z() + (end_p.z() - start_p.z()) / ncount * i + ); + new_pts.push_back(test_p); + + // Check if this point is good + auto test_wpid = dv->contained_by(test_p); + if (test_wpid.face() != -1 && test_wpid.apa() != -1) { + auto temp_p_raw = transform->backward(test_p, cluster_t0, test_wpid.face(), test_wpid.apa()); + if (!grouping->is_good_point(temp_p_raw, test_wpid.apa(), test_wpid.face(), 0.2*units::cm, 0, 0)) { + n_bad++; + } + } + + if (n_bad > 1) { + flag_replace = false; + break; + } + } + + // If the straight line is better, merge the two segments + if (flag_replace) { + std::cout << "Cluster: " << cluster.ident() << " Merge two segments with a straight one, vtx at (" + << vtx->wcpt().point.x()/units::cm << ", " + << vtx->wcpt().point.y()/units::cm << ", " + << vtx->wcpt().point.z()/units::cm << ") cm" << std::endl; + + // Check if the two endpoint vertices are at the same position + double dist_vtx1_vtx2 = ray_length(Ray{vtx1->wcpt().point, vtx2->wcpt().point}); + const double distance_threshold = 0.01 * units::cm; + + if (dist_vtx1_vtx2 < distance_threshold) { + // The two endpoint vertices are the same, merge vtx2 into vtx1 + merge_vertex_into_another(graph, vtx2, vtx1, dv); + } else { + // Create a new segment with straight line path + // Get steiner point cloud + const auto& steiner_pc = cluster.get_pc("steiner_pc"); + const auto& coords = cluster.get_default_scope().coords; + const auto& x_coords = steiner_pc.get(coords.at(0))->elements(); + const auto& y_coords = steiner_pc.get(coords.at(1))->elements(); + const auto& z_coords = steiner_pc.get(coords.at(2))->elements(); + + // Build new WCPoint list with start point + std::vector new_wcpts; + WCPoint start_wcp = vtx1->wcpt(); + WCPoint end_wcp = vtx2->wcpt(); + + new_wcpts.push_back(start_wcp); + + // Add intermediate points from steiner cloud + for (size_t i = 0; i < new_pts.size(); i++) { + auto knn_results = cluster.kd_steiner_knn(1, new_pts[i], "steiner_pc"); + if (!knn_results.empty()) { + size_t idx = knn_results[0].first; + WCPoint wcp; + wcp.point = Facade::geo_point_t(x_coords[idx], y_coords[idx], z_coords[idx]); + + // Only add if different from last point (using distance comparison) + double dist_to_last = ray_length(Ray{wcp.point, new_wcpts.back().point}); + if (dist_to_last > distance_threshold) { + new_wcpts.push_back(wcp); + } + } + } + + // Add end point if not already added (using distance comparison) + double dist_last_to_end = ray_length(Ray{new_wcpts.back().point, end_wcp.point}); + if (dist_last_to_end > distance_threshold) { + new_wcpts.push_back(end_wcp); + } + + // Create new segment with the straight line path + auto new_seg = make_segment(); + new_seg->wcpts(new_wcpts).cluster(&cluster).dirsign(0); + + // Add the new segment to the graph + add_segment(graph, new_seg, vtx1, vtx2); + } + + // Delete the old segments and middle vertex + remove_segment(graph, sg1); + remove_segment(graph, sg2); + remove_vertex(graph, vtx); + + flag_update = true; + flag_continue = true; + break; + } + } + } + + return flag_update; +} + From a806d4b864f8c9a1e23121cdf14d65fea9eb6f57 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Fri, 5 Dec 2025 08:04:00 -0800 Subject: [PATCH 043/111] add examine_structure_4 --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 9 + clus/src/NeutrinoPatternBase.cxx | 133 ++++++++ clus/src/NeutrinoStructureExaminer.cxx | 341 ++++++++++++++++++++ 3 files changed, 483 insertions(+) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index cebdbcb4..a877ee0b 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -34,8 +34,17 @@ namespace WireCell::Clus::PR { // merge vertex into another bool merge_vertex_into_another(Graph& graph, VertexPtr& vtx_from, VertexPtr& vtx_to, IDetectorVolumes::pointer dv); + // get direction with distance cut ... + Facade::geo_vector_t vertex_get_dir(VertexPtr& vertex, Graph& graph, double dis_cut = 5*units::cm); + Facade::geo_vector_t vertex_segment_get_dir(VertexPtr& vertex, SegmentPtr& segment, Graph& graph, double dis_cut = 2*units::cm); + + // Structure examination + void examine_structure(Graph& graph, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); // call examine_structure_1 and examine_structure_2 bool examine_structure_1(Graph& graph, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); bool examine_structure_2(Graph& graph, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); + + bool examine_structure_3(Graph& graph, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); + bool examine_structure_4(VertexPtr vertex, bool flag_final_vertex, Graph& graph, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); }; } diff --git a/clus/src/NeutrinoPatternBase.cxx b/clus/src/NeutrinoPatternBase.cxx index 6fe4c8a5..0a76471e 100644 --- a/clus/src/NeutrinoPatternBase.cxx +++ b/clus/src/NeutrinoPatternBase.cxx @@ -877,4 +877,137 @@ bool PatternAlgorithms::merge_vertex_into_another(Graph& graph, VertexPtr& vtx_f remove_vertex(graph, vtx_from); return true; +} + +Facade::geo_vector_t PatternAlgorithms::vertex_get_dir(VertexPtr& vertex, Graph& graph, double dis_cut){ + if (!vertex || !vertex->cluster()) { + return Facade::geo_vector_t(0, 0, 0); + } + + Facade::geo_point_t center(0, 0, 0); + int ncount = 0; + + // Get vertex position (use fit if available, otherwise wcpt) + Facade::geo_point_t vtx_point = vertex->fit().valid() ? vertex->fit().point : vertex->wcpt().point; + + // Loop through all segments in the graph + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr sg = graph[*eit].segment; + + // Skip if segment doesn't belong to same cluster + if (!sg || sg->cluster() != vertex->cluster()) continue; + + // Get points from segment (skip first and last) + const auto& fits = sg->fits(); + if (fits.size() > 2) { + for (size_t i = 1; i + 1 < fits.size(); i++) { + double dis = std::sqrt(std::pow(fits[i].point.x() - vtx_point.x(), 2) + + std::pow(fits[i].point.y() - vtx_point.y(), 2) + + std::pow(fits[i].point.z() - vtx_point.z(), 2)); + if (dis < dis_cut) { + center = center + Facade::geo_vector_t(fits[i].point.x(), fits[i].point.y(), fits[i].point.z()); + ncount++; + } + } + } + } + + // Loop through all vertices in the graph + auto [vbegin, vend] = boost::vertices(graph); + for (auto vit = vbegin; vit != vend; ++vit) { + VertexPtr other_vtx = graph[*vit].vertex; + + // Skip if vertex doesn't belong to same cluster + if (!other_vtx || other_vtx->cluster() != vertex->cluster()) continue; + + // Get other vertex position + Facade::geo_point_t other_vtx_point = other_vtx->fit().valid() ? other_vtx->fit().point : other_vtx->wcpt().point; + + double dis = std::sqrt(std::pow(other_vtx_point.x() - vtx_point.x(), 2) + + std::pow(other_vtx_point.y() - vtx_point.y(), 2) + + std::pow(other_vtx_point.z() - vtx_point.z(), 2)); + if (dis < dis_cut) { + center = center + Facade::geo_vector_t(other_vtx_point.x(), other_vtx_point.y(), other_vtx_point.z()); + ncount++; + } + } + + if (ncount == 0) { + return Facade::geo_vector_t(0, 0, 0); + } + + // Calculate average center + center = Facade::geo_vector_t(center.x() / ncount, center.y() / ncount, center.z() / ncount); + + // Calculate direction from vertex to center + Facade::geo_vector_t dir(center.x() - vtx_point.x(), + center.y() - vtx_point.y(), + center.z() - vtx_point.z()); + + // Normalize to unit vector + double mag = dir.magnitude(); + if (mag > 0) { + dir = Facade::geo_vector_t(dir.x() / mag, dir.y() / mag, dir.z() / mag); + } + + return dir; +} +Facade::geo_vector_t PatternAlgorithms::vertex_segment_get_dir(VertexPtr& vertex, SegmentPtr& segment, Graph& graph, double dis_cut){ + // Return zero vector if inputs are invalid + if (!vertex || !segment) { + return Facade::geo_vector_t(0, 0, 0); + } + + // Check if this segment is connected to this vertex + if (!vertex->descriptor_valid()) { + return Facade::geo_vector_t(0, 0, 0); + } + + bool segment_connected = false; + auto vd = vertex->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + for (auto eit = edge_range.first; eit != edge_range.second; ++eit) { + if (graph[*eit].segment == segment) { + segment_connected = true; + break; + } + } + + if (!segment_connected) { + return Facade::geo_vector_t(0, 0, 0); + } + + // Get vertex position (use fit if available, otherwise wcpt) + Facade::geo_point_t vtx_point = vertex->fit().valid() ? vertex->fit().point : vertex->wcpt().point; + + // Get points from segment + const auto& pts = segment->wcpts(); + + // Find the point on the segment whose distance from vertex is closest to dis_cut + double min_dis = 1e9; + Facade::geo_point_t min_point = vtx_point; + + for (size_t i = 0; i < pts.size(); i++) { + double tmp_dis = std::sqrt(std::pow(pts[i].point.x() - vtx_point.x(), 2) + + std::pow(pts[i].point.y() - vtx_point.y(), 2) + + std::pow(pts[i].point.z() - vtx_point.z(), 2)); + if (std::fabs(tmp_dis - dis_cut) < min_dis) { + min_dis = std::fabs(tmp_dis - dis_cut); + min_point = pts[i].point; + } + } + + // Calculate direction from vertex to the found point + Facade::geo_vector_t dir(min_point.x() - vtx_point.x(), + min_point.y() - vtx_point.y(), + min_point.z() - vtx_point.z()); + + // Normalize to unit vector + double mag = dir.magnitude(); + if (mag > 0) { + dir = Facade::geo_vector_t(dir.x() / mag, dir.y() / mag, dir.z() / mag); + } + + return dir; } \ No newline at end of file diff --git a/clus/src/NeutrinoStructureExaminer.cxx b/clus/src/NeutrinoStructureExaminer.cxx index e83a9973..a31f15d8 100644 --- a/clus/src/NeutrinoStructureExaminer.cxx +++ b/clus/src/NeutrinoStructureExaminer.cxx @@ -4,6 +4,18 @@ using namespace WireCell::Clus::PR; using namespace WireCell::Clus; +void PatternAlgorithms::examine_structure(Graph& graph, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv){ + // Change 2 to 1 (merge two segments into one straight segment) + if (examine_structure_2(graph, cluster, track_fitter, dv)) { + track_fitter.do_multi_tracking(true, true, true); + } + + // Straighten 1 (replace curved segments with straight lines) + if (examine_structure_1(graph, cluster, track_fitter, dv)) { + track_fitter.do_multi_tracking(true, true, true); + } +} + bool PatternAlgorithms::examine_structure_1(Graph& graph, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv){ // Look at each segment, if the more straight one is better, change it bool flag_update = false; @@ -297,3 +309,332 @@ bool PatternAlgorithms::examine_structure_2(Graph& graph, Facade::Cluster& clust return flag_update; } +bool PatternAlgorithms::examine_structure_3(Graph& graph, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv){ + bool flag_update = false; + bool flag_continue = true; + + while (flag_continue) { + flag_continue = false; + + // Iterate through all vertices in the graph + auto [vbegin, vend] = boost::vertices(graph); + for (auto vit = vbegin; vit != vend; ++vit) { + VertexPtr vtx = graph[*vit].vertex; + + // Skip if vertex doesn't belong to this cluster + if (!vtx || vtx->cluster() != &cluster) continue; + + // Check if this vertex has exactly 2 connected segments + auto vd = vtx->get_descriptor(); + if (boost::degree(vd, graph) != 2) continue; + + // Get the two segments connected to this vertex + auto edge_range = boost::out_edges(vd, graph); + auto eit = edge_range.first; + SegmentPtr sg1 = graph[*eit].segment; + ++eit; + SegmentPtr sg2 = graph[*eit].segment; + + if (!sg1 || !sg2) continue; + + // Get the other vertices (endpoints) + VertexPtr vtx1 = find_other_vertex(graph, sg1, vtx); + VertexPtr vtx2 = find_other_vertex(graph, sg2, vtx); + + if (!vtx1 || !vtx2) continue; + + // Get the vertex position (use fit if available, otherwise wcpt) + WireCell::Point vtx_point = vtx->fit().valid() ? vtx->fit().point : vtx->wcpt().point; + + // Calculate direction vectors at different distances + WireCell::Vector dir1 = segment_cal_dir_3vector(sg1, vtx_point, 10*units::cm); + WireCell::Vector dir2 = segment_cal_dir_3vector(sg2, vtx_point, 10*units::cm); + + WireCell::Vector dir3 = segment_cal_dir_3vector(sg1, vtx_point, 3*units::cm); + WireCell::Vector dir4 = segment_cal_dir_3vector(sg2, vtx_point, 3*units::cm); + + // Calculate angles (180 - angle between directions) + double angle_10cm = (3.1415926 - std::acos(dir1.dot(dir2) / (dir1.magnitude() * dir2.magnitude()))) / 3.1415926 * 180.0; + double angle_3cm = (3.1415926 - std::acos(dir3.dot(dir4) / (dir3.magnitude() * dir4.magnitude()))) / 3.1415926 * 180.0; + + // Check if segments are nearly collinear (small angles) + if (angle_10cm < 18 && angle_3cm < 27) { + std::cout << "Cluster: " << cluster.ident() << " Merge two segments into one according to angle " + << angle_10cm << "° (10cm) and " << angle_3cm << "° (3cm)" << std::endl; + + // Merge the two segments by combining their WCPoint lists + const auto& wcpts1 = sg1->wcpts(); + const auto& wcpts2 = sg2->wcpts(); + + std::vector merged_wcpts; + const double distance_threshold = 0.01 * units::cm; + + // Determine how to merge based on which endpoints connect + double dist_front1_front2 = ray_length(Ray{wcpts1.front().point, wcpts2.front().point}); + double dist_front1_back2 = ray_length(Ray{wcpts1.front().point, wcpts2.back().point}); + double dist_back1_front2 = ray_length(Ray{wcpts1.back().point, wcpts2.front().point}); + double dist_back1_back2 = ray_length(Ray{wcpts1.back().point, wcpts2.back().point}); + + if (dist_front1_front2 < distance_threshold) { + // front1 connects to front2: reverse wcpts2, skip duplicate, add wcpts1 + for (auto it = wcpts2.rbegin(); it != wcpts2.rend(); ++it) { + merged_wcpts.push_back(*it); + } + // Skip first point of wcpts1 as it's the same as last added + for (size_t i = 1; i < wcpts1.size(); ++i) { + merged_wcpts.push_back(wcpts1[i]); + } + } else if (dist_front1_back2 < distance_threshold) { + // front1 connects to back2: add wcpts2, skip duplicate, add wcpts1 + for (const auto& wcp : wcpts2) { + merged_wcpts.push_back(wcp); + } + // Skip first point of wcpts1 as it's the same as last added + for (size_t i = 1; i < wcpts1.size(); ++i) { + merged_wcpts.push_back(wcpts1[i]); + } + } else if (dist_back1_front2 < distance_threshold) { + // back1 connects to front2: add wcpts1, skip duplicate, add wcpts2 + for (const auto& wcp : wcpts1) { + merged_wcpts.push_back(wcp); + } + // Skip first point of wcpts2 as it's the same as last added + for (size_t i = 1; i < wcpts2.size(); ++i) { + merged_wcpts.push_back(wcpts2[i]); + } + } else if (dist_back1_back2 < distance_threshold) { + // back1 connects to back2: add wcpts1, skip duplicate, reverse wcpts2 + for (const auto& wcp : wcpts1) { + merged_wcpts.push_back(wcp); + } + // Skip last point of wcpts2 (reverse order) as it's the same as last added + for (auto it = wcpts2.rbegin() + 1; it != wcpts2.rend(); ++it) { + merged_wcpts.push_back(*it); + } + } + + // Create new segment with merged points + auto new_seg = make_segment(); + new_seg->wcpts(merged_wcpts).cluster(&cluster).dirsign(0); + + // Add the new segment to the graph + add_segment(graph, new_seg, vtx1, vtx2); + + // Delete the old segments and middle vertex + remove_segment(graph, sg1); + remove_segment(graph, sg2); + remove_vertex(graph, vtx); + + flag_update = true; + flag_continue = true; + break; + } + } + } + + return flag_update; +} + +bool PatternAlgorithms::examine_structure_4(VertexPtr vertex, bool flag_final_vertex, Graph& graph, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv){ + bool flag_update = false; + + // Get transform and grouping from track_fitter + const auto transform = track_fitter.get_pc_transforms()->pc_transform( + cluster.get_scope_transform(cluster.get_default_scope())); + double cluster_t0 = cluster.get_cluster_t0(); + auto grouping = cluster.grouping(); + + if (!transform || !grouping) { + return false; + } + + // Check if vertex belongs to this cluster + if (!vertex || vertex->cluster() != &cluster) return false; + + // Check vertex degree + auto vd = vertex->get_descriptor(); + int degree = boost::degree(vd, graph); + if (degree < 2 && !flag_final_vertex) return false; + + // Get steiner point cloud and flag terminals + const auto& steiner_pc = cluster.get_pc("steiner_pc"); + const auto& coords = cluster.get_default_scope().coords; + const auto& x_coords = steiner_pc.get(coords.at(0))->elements(); + const auto& y_coords = steiner_pc.get(coords.at(1))->elements(); + const auto& z_coords = steiner_pc.get(coords.at(2))->elements(); + const auto& flag_terminals = steiner_pc.get("flag_steiner_terminal")->elements(); + + // Get vertex position (use fit if available, otherwise wcpt) + WireCell::Point vtx_point = vertex->fit().valid() ? vertex->fit().point : vertex->wcpt().point; + + // Find candidate wcps within 6 cm radius + auto candidate_results = cluster.kd_steiner_radius(6*units::cm, vtx_point, "steiner_pc"); + + double max_dis = 0; + WireCell::Point max_wcp_point; + bool found_candidate = false; + + // Loop over candidate points + for (const auto& [idx, dist_sq] : candidate_results) { + // Check if this is a steiner terminal + if (!flag_terminals[idx]) continue; + + WireCell::Point test_p(x_coords[idx], y_coords[idx], z_coords[idx]); + double dis = std::sqrt(std::pow(test_p.x() - vtx_point.x(), 2) + + std::pow(test_p.y() - vtx_point.y(), 2) + + std::pow(test_p.z() - vtx_point.z(), 2)); + + // Find minimum distances to all segments + double min_dis = 1e9; + double min_dis_u = 1e9; + double min_dis_v = 1e9; + double min_dis_w = 1e9; + + // Check against all segments in the graph + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr sg = graph[*eit].segment; + if (!sg) continue; + + // Get 3D closest point distance + auto [dist_3d, closest_pt] = segment_get_closest_point(sg, test_p, "fit"); + if (dist_3d < min_dis) min_dis = dist_3d; + + // Get closest wcpt distance + const auto& wcpts = sg->wcpts(); + for (const auto& wcp : wcpts) { + double tmp_dis = std::sqrt(std::pow(wcp.point.x() - test_p.x(), 2) + + std::pow(wcp.point.y() - test_p.y(), 2) + + std::pow(wcp.point.z() - test_p.z(), 2)); + if (tmp_dis < min_dis) min_dis = tmp_dis; + } + + // Get 2D distances - need to determine apa and face + auto test_wpid = dv->contained_by(test_p); + if (test_wpid.face() != -1 && test_wpid.apa() != -1) { + auto [dist_u, dist_v, dist_w] = segment_get_closest_2d_distances(sg, test_p, test_wpid.apa(), test_wpid.face(), "fit"); + if (dist_u < min_dis_u) min_dis_u = dist_u; + if (dist_v < min_dis_v) min_dis_v = dist_v; + if (dist_w < min_dis_w) min_dis_w = dist_w; + } + } + + // Check distance criteria + if (min_dis > 0.9*units::cm && + min_dis_u + min_dis_v + min_dis_w > 1.8*units::cm && + ((min_dis_u > 0.8*units::cm && min_dis_v > 0.8*units::cm) || + (min_dis_u > 0.8*units::cm && min_dis_w > 0.8*units::cm) || + (min_dis_v > 0.8*units::cm && min_dis_w > 0.8*units::cm))) { + + // Test points along straight line for good points + double step_size = 0.3 * units::cm; + WireCell::Point start_p = vtx_point; + WireCell::Point end_p = test_p; + double distance = std::sqrt(std::pow(start_p.x() - end_p.x(), 2) + + std::pow(start_p.y() - end_p.y(), 2) + + std::pow(start_p.z() - end_p.z(), 2)); + int ncount = std::round(distance / step_size); + + bool flag_pass = true; + int n_bad = 0; + + for (int j = 1; j < ncount; j++) { + WireCell::Point test_p1( + start_p.x() + (end_p.x() - start_p.x()) / ncount * j, + start_p.y() + (end_p.y() - start_p.y()) / ncount * j, + start_p.z() + (end_p.z() - start_p.z()) / ncount * j + ); + + auto test_wpid = dv->contained_by(test_p1); + if (test_wpid.face() != -1 && test_wpid.apa() != -1) { + auto temp_p_raw = transform->backward(test_p1, cluster_t0, test_wpid.face(), test_wpid.apa()); + if (!grouping->is_good_point(temp_p_raw, test_wpid.apa(), test_wpid.face(), 0.3*units::cm, 0, 0)) { + n_bad++; + } + } + + if (n_bad > 0) { + flag_pass = false; + break; + } + } + + if (flag_pass) { + if (max_dis < dis) { + max_dis = dis; + max_wcp_point = test_p; + max_wcp_idx = idx; + found_candidate = true; + } + } + } + } + + // If we found a good candidate, create a new segment + if (found_candidate && max_dis > 1.6*units::cm) { + // Create new vertex at the terminal point + VertexPtr v1 = make_vertex(graph); + WCPoint new_wcp; + new_wcp.point = max_wcp_point; + v1->wcpt(new_wcp); + v1->cluster(&cluster); + + // Build wcpoint list for the new segment + std::vector wcp_list; + wcp_list.push_back(vertex->wcpt()); + + // Add intermediate points with 1 cm steps + double step_size = 1.0 * units::cm; + WireCell::Point start_p = vtx_point; + WireCell::Point end_p = max_wcp_point; + double distance = std::sqrt(std::pow(start_p.x() - end_p.x(), 2) + + std::pow(start_p.y() - end_p.y(), 2) + + std::pow(start_p.z() - end_p.z(), 2)); + int ncount = std::round(distance / step_size); + + const double distance_threshold = 0.01 * units::cm; + + for (int j = 1; j < ncount; j++) { + WireCell::Point tmp_p( + start_p.x() + (end_p.x() - start_p.x()) / ncount * j, + start_p.y() + (end_p.y() - start_p.y()) / ncount * j, + start_p.z() + (end_p.z() - start_p.z()) / ncount * j + ); + + auto knn_results = cluster.kd_steiner_knn(1, tmp_p, "steiner_pc"); + if (!knn_results.empty()) { + size_t idx = knn_results[0].first; + WCPoint wcp; + wcp.point = WireCell::Point(x_coords[idx], y_coords[idx], z_coords[idx]); + + // Only add if different from last point + double dist_to_last = ray_length(Ray{wcp.point, wcp_list.back().point}); + if (dist_to_last > distance_threshold) { + wcp_list.push_back(wcp); + } + } + } + + // Add end point if not already added + WCPoint end_wcp; + end_wcp.point = max_wcp_point; + double dist_last_to_end = ray_length(Ray{wcp_list.back().point, end_wcp.point}); + if (dist_last_to_end > distance_threshold) { + wcp_list.push_back(end_wcp); + } + + std::cout << "Cluster: " << cluster.ident() << " Add a track to the main vertex, " + << wcp_list.size() << " points" << std::endl; + + // Create new segment + auto sg1 = make_segment(); + sg1->wcpts(wcp_list).cluster(&cluster).dirsign(0); + + // Add segment to graph + add_segment(graph, sg1, v1, vertex); + + flag_update = true; + } + + return flag_update; +} \ No newline at end of file From a07e55b1f7e7a0a16ab31acb38a2e6407b2f1688 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Fri, 5 Dec 2025 09:05:37 -0800 Subject: [PATCH 044/111] implement the find_other_segments --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 5 ++++- clus/src/NeutrinoPatternBase.cxx | 3 ++- clus/src/NeutrinoStructureExaminer.cxx | 2 +- clus/src/TrackFitting.cxx | 22 ++++++++++----------- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index a877ee0b..6aade6c6 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -28,7 +28,7 @@ namespace WireCell::Clus::PR { // return Steiner Graph path in wcps_list1 and wcps_list2 bool proto_break_tracks(const Facade::Cluster& cluster, const Facade::geo_point_t& first_wcp, const Facade::geo_point_t& curr_wcp, const Facade::geo_point_t& last_wcp, std::list& wcps_list1, std::list& wcps_list2, bool flag_pass_check); // breaking segments ... - bool break_segments(Graph& graph, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, std::vector& remaining_segments, float dis_cut); + bool break_segments(Graph& graph, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, std::vector& remaining_segments, float dis_cut = 0); // merge two segments to one bool merge_two_segments_into_one(Graph& graph, SegmentPtr& seg1, VertexPtr& vtx, SegmentPtr& seg2, IDetectorVolumes::pointer dv); // merge vertex into another @@ -46,5 +46,8 @@ namespace WireCell::Clus::PR { bool examine_structure_3(Graph& graph, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); bool examine_structure_4(VertexPtr vertex, bool flag_final_vertex, Graph& graph, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); + + // identify other segments giving the graph ... + void find_other_segments(Graph& graph, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv , bool flag_break_track =true, double search_range = 1.5*units::cm, double scaling_2d = 0.8); }; } diff --git a/clus/src/NeutrinoPatternBase.cxx b/clus/src/NeutrinoPatternBase.cxx index 0a76471e..a6b2d61c 100644 --- a/clus/src/NeutrinoPatternBase.cxx +++ b/clus/src/NeutrinoPatternBase.cxx @@ -1010,4 +1010,5 @@ Facade::geo_vector_t PatternAlgorithms::vertex_segment_get_dir(VertexPtr& vertex } return dir; -} \ No newline at end of file +} + diff --git a/clus/src/NeutrinoStructureExaminer.cxx b/clus/src/NeutrinoStructureExaminer.cxx index a31f15d8..771a7118 100644 --- a/clus/src/NeutrinoStructureExaminer.cxx +++ b/clus/src/NeutrinoStructureExaminer.cxx @@ -563,7 +563,7 @@ bool PatternAlgorithms::examine_structure_4(VertexPtr vertex, bool flag_final_ve if (max_dis < dis) { max_dis = dis; max_wcp_point = test_p; - max_wcp_idx = idx; + // max_wcp_idx = idx; found_candidate = true; } } diff --git a/clus/src/TrackFitting.cxx b/clus/src/TrackFitting.cxx index 6ebef7ac..c8848cf8 100644 --- a/clus/src/TrackFitting.cxx +++ b/clus/src/TrackFitting.cxx @@ -931,7 +931,7 @@ std::vector TrackFitting::generate_fits_with_projections( int apa = test_wpid.apa(); int face = test_wpid.face(); - auto p_raw = transform->backward(pts.at(i), cluster_t0, apa, face); + auto p_raw = transform->backward(pts.at(i), cluster_t0, face, apa); WirePlaneId wpid(kAllLayers, face, apa); auto offset_it = wpid_offsets.find(wpid); auto slope_it = wpid_slopes.find(wpid); @@ -1717,7 +1717,7 @@ void TrackFitting::organize_ps_path(std::shared_ptr segment, std::v if (temp_dis < dis_cut){ - // auto p_raw = transform->backward(p, cluster_t0, apa, face); + // auto p_raw = transform->backward(p, cluster_t0, face, apa); // std::cout << "WirePlaneId: " << wpid << ", Angles: (" << angle_u << ", " << angle_v << ", " << angle_w << ")" << " " << time_tick_width/units::cm << " " << pitch_u/units::cm << " " << pitch_v/units::cm << " " << pitch_w/units::cm << std::endl; // Get graph algorithms interface // auto cached_gas = cluster->get_cached_graph_algorithms(); @@ -1933,7 +1933,7 @@ void TrackFitting::organize_ps_path(std::shared_ptr segment, std::v auto total_vertices_found = ga.find_neighbors_nlevel(closest_point_index, nlevel); // find the raw point ... - auto closest_point_raw = transform->backward(closest_point, cluster_t0, apa, face); + auto closest_point_raw = transform->backward(closest_point, cluster_t0, face, apa); // std::cout << p << " " << closest_point << " " << closest_point_raw << std::endl; @@ -1972,7 +1972,7 @@ void TrackFitting::organize_ps_path(std::shared_ptr segment, std::v y_coords[vertex_idx], z_coords[vertex_idx]}; - auto vertex_point_raw = transform->backward(vertex_point, cluster_t0, apa, face); + auto vertex_point_raw = transform->backward(vertex_point, cluster_t0, face, apa); auto vertex_u = m_grouping->convert_3Dpoint_time_ch(vertex_point_raw, apa, face, 0); auto vertex_v = m_grouping->convert_3Dpoint_time_ch(vertex_point_raw, apa, face, 1); auto vertex_w = m_grouping->convert_3Dpoint_time_ch(vertex_point_raw, apa, face, 2); @@ -2364,7 +2364,7 @@ void TrackFitting::update_association(std::shared_ptr segment, Plan if (apa == -1 || face == -1) return; // // Convert 3D point to wire/time coordinates for each plane - geo_point_t p_raw = transform->backward(p, cluster_t0, apa, face); + geo_point_t p_raw = transform->backward(p, cluster_t0, face, apa); auto [time_u, wire_u] = m_grouping->convert_3Dpoint_time_ch(p_raw, apa, face, 0); auto [time_v, wire_v] = m_grouping->convert_3Dpoint_time_ch(p_raw, apa, face, 1); auto [time_w, wire_w] = m_grouping->convert_3Dpoint_time_ch(p_raw, apa, face, 2); @@ -3486,7 +3486,7 @@ void TrackFitting::multi_trajectory_fit(int charge_div_method, double div_sigma) vertex_fit.index = -1; // Store 2D projections (following WCP pattern with offsets) - auto fitted_p_raw = transform->backward(fitted_p, cluster_t0, test_wpid.apa(), test_wpid.face()); + auto fitted_p_raw = transform->backward(fitted_p, cluster_t0, test_wpid.face(), test_wpid.apa()); vertex_fit.pu = offset_u + (slope_yu * fitted_p_raw.y() + slope_zu * fitted_p_raw.z()); vertex_fit.pv = offset_v + (slope_yv * fitted_p_raw.y() + slope_zv * fitted_p_raw.z()); vertex_fit.pw = offset_w + (slope_yw * fitted_p_raw.y() + slope_zw * fitted_p_raw.z()); @@ -3614,7 +3614,7 @@ void TrackFitting::multi_trajectory_fit(int charge_div_method, double div_sigma) auto offset_it = wpid_offsets.find(wpid); auto slope_it = wpid_slopes.find(wpid); - auto examined_p_raw = transform->backward(examined_ps[i], cluster_t0, test_wpid.apa(), test_wpid.face()); + auto examined_p_raw = transform->backward(examined_ps[i], cluster_t0, test_wpid.face(), test_wpid.apa()); fit.paf = std::make_pair(test_wpid.apa(), test_wpid.face()); if (offset_it != wpid_offsets.end() && slope_it != wpid_slopes.end()) { @@ -4193,7 +4193,7 @@ void TrackFitting::trajectory_fit(std::vectorbackward(p, cluster_t0, apa, face); + auto p_raw = transform->backward(p, cluster_t0, face, apa); WirePlaneId wpid(kAllLayers, face, apa); auto offset_it = wpid_offsets.find(wpid); auto slope_it = wpid_slopes.find(wpid); @@ -6552,7 +6552,7 @@ void WireCell::Clus::TrackFitting::dQ_dx_fit(double dis_end_point_ext, bool flag // First half (prev to curr) WireCell::Point reco_pos = prev_rec_pos + (curr_rec_pos - prev_rec_pos) * (j + 0.5) / 5.0; // find out the raw position ... - auto reco_pos_raw = transform->backward(reco_pos, cluster_t0, apa, face); + auto reco_pos_raw = transform->backward(reco_pos, cluster_t0, face, apa); double central_T = offset_t + slope_x * reco_pos_raw.x(); double central_U = offset_u + (slope_yu * reco_pos_raw.y() + slope_zu * reco_pos_raw.z()); @@ -6583,7 +6583,7 @@ void WireCell::Clus::TrackFitting::dQ_dx_fit(double dis_end_point_ext, bool flag // Second half (curr to next) reco_pos = next_rec_pos + (curr_rec_pos - next_rec_pos) * (j + 0.5) / 5.0; - reco_pos_raw = transform->backward(reco_pos, cluster_t0, apa, face); + reco_pos_raw = transform->backward(reco_pos, cluster_t0, face, apa); central_T = offset_t + slope_x * reco_pos_raw.x(); central_U = offset_u + (slope_yu * reco_pos_raw.y() + slope_zu * reco_pos_raw.z()); @@ -7611,7 +7611,7 @@ void TrackFitting::do_single_tracking(std::shared_ptr segment, bool int apa = test_wpid.apa(); int face = test_wpid.face(); - auto p_raw = transform->backward(p, cluster_t0, apa, face); + auto p_raw = transform->backward(p, cluster_t0, face, apa); WirePlaneId wpid(kAllLayers, face, apa); auto offset_it = wpid_offsets.find(wpid); auto slope_it = wpid_slopes.find(wpid); From f59f3e9a2f61fe0f2426ab0fc056b62183f307dc Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Fri, 5 Dec 2025 09:05:44 -0800 Subject: [PATCH 045/111] implement the find_other_segments --- clus/src/NeutrinoOtherSegments.cxx | 495 +++++++++++++++++++++++++++++ 1 file changed, 495 insertions(+) create mode 100644 clus/src/NeutrinoOtherSegments.cxx diff --git a/clus/src/NeutrinoOtherSegments.cxx b/clus/src/NeutrinoOtherSegments.cxx new file mode 100644 index 00000000..89890029 --- /dev/null +++ b/clus/src/NeutrinoOtherSegments.cxx @@ -0,0 +1,495 @@ +#include "WireCellClus/NeutrinoPatternBase.h" +#include "WireCellClus/PRSegmentFunctions.h" +// #include "WireCellClus/Graphs/Weighted.h" + +using namespace WireCell::Clus::PR; +using namespace WireCell::Clus; + +// Edge property tag for Boost Graph +struct edge_base_t { + typedef boost::edge_property_tag kind; +}; + +// Helper struct to track segment candidates +struct Res_proto_segment { + int group_num; + int number_points; + size_t special_A; + size_t special_B; + double length; + int number_not_faked; + double max_dis_u; + double max_dis_v; + double max_dis_w; +}; + +void PatternAlgorithms::find_other_segments(Graph& graph, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, bool flag_break_track, double search_range, double scaling_2d) +{ + // Get steiner point cloud data + const auto& steiner_pc = cluster.get_pc("steiner_pc"); + const auto& coords = cluster.get_default_scope().coords; + const auto& x_coords = steiner_pc.get(coords.at(0))->elements(); + const auto& y_coords = steiner_pc.get(coords.at(1))->elements(); + const auto& z_coords = steiner_pc.get(coords.at(2))->elements(); + const auto& wpid_array = steiner_pc.get("wpid")->elements(); + + const size_t N = x_coords.size(); + if (N == 0) return; + + // Step 1: Tag points near existing segments + std::vector flag_tagged(N, false); + // int num_tagged = 0; + + const auto transform = track_fitter.get_pc_transforms()->pc_transform(cluster.get_scope_transform(cluster.get_default_scope())); + double cluster_t0 = cluster.get_cluster_t0(); + + // Get all existing segments in this cluster + std::set existing_segments = find_cluster_segments(graph, cluster); + + for (size_t i = 0; i < N; i++) { + Facade::geo_point_t p(x_coords[i], y_coords[i], z_coords[i]); + double min_dis_u = 1e9, min_dis_v = 1e9, min_dis_w = 1e9; + double min_3d_dis = 1e9; + + WirePlaneId wpid = wpid_array[i]; + int apa = wpid.apa(); + int face = wpid.face(); + + // Check distances to existing segments + for (auto seg : existing_segments) { + // Get closest 3D point + auto closest_result = segment_get_closest_point(seg, p, "fit"); + double dis_3d = closest_result.first; // distance is already computed + + if (dis_3d < min_3d_dis) min_3d_dis = dis_3d; + + if (dis_3d < search_range) { + flag_tagged[i] = true; + // num_tagged++; + break; + } + + // Get 2D distances + auto closest_2d = segment_get_closest_2d_distances(seg, p, apa, face, "fit"); + double dis_u = std::get<0>(closest_2d); + double dis_v = std::get<1>(closest_2d); + double dis_w = std::get<2>(closest_2d); + + if (dis_u < min_dis_u) min_dis_u = dis_u; + if (dis_v < min_dis_v) min_dis_v = dis_v; + if (dis_w < min_dis_w) min_dis_w = dis_w; + } + + // Additional tagging based on 2D projections and dead channels + if (!flag_tagged[i]) { + auto p_raw = transform->backward(p, cluster_t0, face, apa); + + bool u_ok = (min_dis_u < scaling_2d * search_range || + cluster.grouping()->get_closest_dead_chs(p_raw, 1, apa, face, 0)); + bool v_ok = (min_dis_v < scaling_2d * search_range || + cluster.grouping()->get_closest_dead_chs(p_raw, 1, apa, face, 1)); + bool w_ok = (min_dis_w < scaling_2d * search_range || + cluster.grouping()->get_closest_dead_chs(p_raw, 1, apa, face, 2)); + + if (u_ok && v_ok && w_ok) { + flag_tagged[i] = true; + } + } + } + + // Step 2: Get terminal vertices + const auto& flag_steiner_terminal = steiner_pc.get("flag_steiner_terminal")->elements(); + std::vector terminals; + std::map map_oindex_tindex; + + for (size_t i = 0; i < flag_steiner_terminal.size(); i++) { + if (flag_steiner_terminal[i]) { + map_oindex_tindex[i] = terminals.size(); + terminals.push_back(i); + } + } + + if (terminals.empty()) return; + + // Step 3: Compute Voronoi diagram + const auto& steiner_graph = cluster.get_graph("steiner_graph"); + using namespace Graphs::Weighted; + auto vor = voronoi(steiner_graph, terminals); + + // Step 4: Build terminal graph with MST + using Base = boost::property; + using WeightProperty = boost::property; + using TerminalGraph = boost::adjacency_list; + + TerminalGraph terminal_graph(N); + std::map, std::pair> map_saved_edge; + + auto edge_weight = get(boost::edge_weight, steiner_graph); + + for (auto w : boost::make_iterator_range(edges(steiner_graph))) { + size_t nearest_to_source = vor.terminal[source(w, steiner_graph)]; + size_t nearest_to_target = vor.terminal[target(w, steiner_graph)]; + + if (nearest_to_source != nearest_to_target) { + double weight = vor.distance[source(w, steiner_graph)] + + vor.distance[target(w, steiner_graph)] + + edge_weight[w]; + + auto edge_pair1 = std::make_pair(nearest_to_source, nearest_to_target); + auto edge_pair2 = std::make_pair(nearest_to_target, nearest_to_source); + + auto it1 = map_saved_edge.find(edge_pair1); + auto it2 = map_saved_edge.find(edge_pair2); + + if (it1 != map_saved_edge.end()) { + if (weight < it1->second.first) { + it1->second = std::make_pair(weight, w); + } + } else if (it2 != map_saved_edge.end()) { + if (weight < it2->second.first) { + it2->second = std::make_pair(weight, w); + } + } else { + map_saved_edge[edge_pair1] = std::make_pair(weight, w); + } + } + } + + // Add edges to terminal graph + for (const auto& [edge_pair, weight_info] : map_saved_edge) { + boost::add_edge(edge_pair.first, edge_pair.second, + WeightProperty(weight_info.first, Base(weight_info.second)), + terminal_graph); + } + + // Step 5: Find minimum spanning tree + std::vector::edge_descriptor> mst_edges; + boost::kruskal_minimum_spanning_tree(terminal_graph, std::back_inserter(mst_edges)); + + // Step 6: Create cluster graph based on tagging + TerminalGraph terminal_graph_cluster(terminals.size()); + std::map> map_connection; + + for (const auto& edge : mst_edges) { + size_t source_idx = boost::source(edge, terminal_graph); + size_t target_idx = boost::target(edge, terminal_graph); + + if (flag_tagged[source_idx] == flag_tagged[target_idx]) { + boost::add_edge(map_oindex_tindex[source_idx], + map_oindex_tindex[target_idx], + terminal_graph_cluster); + } else { + if (map_connection.find(source_idx) == map_connection.end()) { + std::set temp_results; + temp_results.insert(target_idx); + map_connection[source_idx] = temp_results; + } else { + map_connection[source_idx].insert(target_idx); + } + + if (map_connection.find(target_idx) == map_connection.end()) { + std::set temp_results; + temp_results.insert(source_idx); + map_connection[target_idx] = temp_results; + } else { + map_connection[target_idx].insert(source_idx); + } + } + } + + // Step 7: Find connected components + std::vector component(boost::num_vertices(terminal_graph_cluster)); + const int num_components = boost::connected_components(terminal_graph_cluster, &component[0]); + + std::vector ncounts(num_components, 0); + std::vector> sep_clusters(num_components); + + for (size_t i = 0; i < component.size(); ++i) { + ncounts[component[i]]++; + sep_clusters[component[i]].push_back(terminals[i]); + } + + // Step 8: Analyze each cluster and filter + std::vector temp_segments(num_components); + std::set remaining_segments; + + for (int i = 0; i < num_components; i++) { + // Skip if inside original track + if (flag_tagged[sep_clusters[i].front()]) { + continue; + } + + remaining_segments.insert(i); + temp_segments[i].group_num = i; + temp_segments[i].number_points = ncounts[i]; + + // Find connection point (special_A) + size_t special_A = SIZE_MAX; + std::vector candidates_special_A; + + for (int j = 0; j < ncounts[i]; j++) { + if (map_connection.find(sep_clusters[i][j]) != map_connection.end()) { + candidates_special_A.push_back(sep_clusters[i][j]); + } + } + + if (!candidates_special_A.empty()) { + special_A = candidates_special_A.front(); + } + + // Find furthest point (special_B) + size_t special_B = special_A; + double min_dis = 0; + int number_not_faked = 0; + double max_dis_u = 0, max_dis_v = 0, max_dis_w = 0; + + for (int j = 0; j < ncounts[i]; j++) { + size_t idx = sep_clusters[i][j]; + double dis = std::sqrt( + std::pow(x_coords[idx] - x_coords[special_A], 2) + + std::pow(y_coords[idx] - y_coords[special_A], 2) + + std::pow(z_coords[idx] - z_coords[special_A], 2)); + + if (dis > min_dis) { + min_dis = dis; + special_B = idx; + } + + // Check if point is fake (too close to existing segments) + Facade::geo_point_t p(x_coords[idx], y_coords[idx], z_coords[idx]); + double min_dis_u = 1e9, min_dis_v = 1e9, min_dis_w = 1e9; + + WirePlaneId wpid = wpid_array[idx]; + int apa = wpid.apa(); + int face = wpid.face(); + + for (auto seg : existing_segments) { + auto closest_2d = segment_get_closest_2d_distances(seg, p, apa, face, "fit"); + double dis_u = std::get<0>(closest_2d); + double dis_v = std::get<1>(closest_2d); + double dis_w = std::get<2>(closest_2d); + + if (dis_u < min_dis_u) min_dis_u = dis_u; + if (dis_v < min_dis_v) min_dis_v = dis_v; + if (dis_w < min_dis_w) min_dis_w = dis_w; + } + + auto p_raw = transform->backward(p, cluster_t0, face, apa); + + int flag_num = 0; + if (min_dis_u > scaling_2d * search_range && + !cluster.grouping()->get_closest_dead_chs(p_raw, 1, apa, face, 0)) flag_num++; + if (min_dis_v > scaling_2d * search_range && + !cluster.grouping()->get_closest_dead_chs(p_raw, 1, apa, face, 1)) flag_num++; + if (min_dis_w > scaling_2d * search_range && + !cluster.grouping()->get_closest_dead_chs(p_raw, 1, apa, face, 2)) flag_num++; + + if (min_dis_u > max_dis_u && + !cluster.grouping()->get_closest_dead_chs(p_raw, 1, apa, face, 0)) max_dis_u = min_dis_u; + if (min_dis_v > max_dis_v && + !cluster.grouping()->get_closest_dead_chs(p_raw, 1, apa, face, 1)) max_dis_v = min_dis_v; + if (min_dis_w > max_dis_w && + !cluster.grouping()->get_closest_dead_chs(p_raw, 1, apa, face, 2)) max_dis_w = min_dis_w; + + if (flag_num >= 2) number_not_faked++; + } + + double length = std::sqrt( + std::pow(x_coords[special_A] - x_coords[special_B], 2) + + std::pow(y_coords[special_A] - y_coords[special_B], 2) + + std::pow(z_coords[special_A] - z_coords[special_B], 2)); + + // Adjust special_A if length is too short + if (length < 3 * units::cm && special_A != SIZE_MAX) { + size_t save_index = special_A; + double save_dis = 1e9; + for (auto it1 = map_connection[special_A].begin(); + it1 != map_connection[special_A].end(); it1++) { + double temp_dis = std::sqrt( + std::pow(x_coords[special_A] - x_coords[*it1], 2) + + std::pow(y_coords[special_A] - y_coords[*it1], 2) + + std::pow(z_coords[special_A] - z_coords[*it1], 2)); + if (temp_dis < save_dis) { + save_index = *it1; + save_dis = temp_dis; + } + } + special_A = save_index; + length = std::sqrt( + std::pow(x_coords[special_A] - x_coords[special_B], 2) + + std::pow(y_coords[special_A] - y_coords[special_B], 2) + + std::pow(z_coords[special_A] - z_coords[special_B], 2)); + } + + temp_segments[i].special_A = special_A; + temp_segments[i].special_B = special_B; + temp_segments[i].length = length; + temp_segments[i].number_not_faked = number_not_faked; + temp_segments[i].max_dis_u = max_dis_u; + temp_segments[i].max_dis_v = max_dis_v; + temp_segments[i].max_dis_w = max_dis_w; + + // Apply quality cuts + if ((temp_segments[i].number_points == 1) || + (number_not_faked == 0 && + ((length < 3.5 * units::cm) || + (((number_not_faked < 0.25 * temp_segments[i].number_points) || + (number_not_faked < 0.4 * temp_segments[i].number_points && length < 7 * units::cm)) && + max_dis_u / units::cm < 3 && max_dis_v / units::cm < 3 && max_dis_w / units::cm < 3 && + max_dis_u + max_dis_v + max_dis_w < 6 * units::cm)))) { + remaining_segments.erase(i); + } + } + + // Step 9: Process remaining segments in order of quality + std::vector new_segments_for_tracking; + + while (!remaining_segments.empty()) { + // Find the best segment (most non-faked points, then longest) + double max_number_not_faked = 0; + double max_length = 0; + int max_length_cluster = -1; + + for (auto it = remaining_segments.begin(); it != remaining_segments.end(); it++) { + if (temp_segments[*it].number_not_faked > max_number_not_faked) { + max_length_cluster = *it; + max_number_not_faked = temp_segments[*it].number_not_faked; + max_length = temp_segments[*it].length; + } else if (temp_segments[*it].number_not_faked == max_number_not_faked) { + if (temp_segments[*it].length > max_length) { + max_length_cluster = *it; + max_number_not_faked = temp_segments[*it].number_not_faked; + max_length = temp_segments[*it].length; + } + } + } + + if (max_length_cluster == -1) break; + + remaining_segments.erase(max_length_cluster); + + // Create new segment + size_t special_A = temp_segments[max_length_cluster].special_A; + size_t special_B = temp_segments[max_length_cluster].special_B; + + if (special_A == SIZE_MAX || special_B == SIZE_MAX) continue; + + // Use Dijkstra to find path + auto path_indices = cluster.graph_algorithms("steiner_graph").shortest_path(special_A, special_B); + + std::vector path_points; + for (size_t idx : path_indices) { + path_points.emplace_back(x_coords[idx], y_coords[idx], z_coords[idx]); + } + + if (path_points.size() <= 1) continue; + + // Create vertices + VertexPtr v1 = make_vertex(graph); + v1->wcpt().point = path_points.front(); + v1->cluster(&cluster); + + VertexPtr v2 = make_vertex(graph); + v2->wcpt().point = path_points.back(); + v2->cluster(&cluster); + + // Create segment + auto new_seg = create_segment_for_cluster(cluster, dv, path_points); + if (!new_seg) { + remove_vertex(graph, v1); + remove_vertex(graph, v2); + continue; + } + + add_segment(graph, new_seg, v1, v2); + new_segments_for_tracking.push_back(new_seg); + existing_segments.insert(new_seg); + + // Re-evaluate remaining segments + std::set tmp_del_set; + for (auto it = remaining_segments.begin(); it != remaining_segments.end(); it++) { + temp_segments[*it].number_not_faked = 0; + temp_segments[*it].max_dis_u = 0; + temp_segments[*it].max_dis_v = 0; + temp_segments[*it].max_dis_w = 0; + + for (int j = 0; j < ncounts[*it]; j++) { + size_t idx = sep_clusters[*it][j]; + Facade::geo_point_t p(x_coords[idx], y_coords[idx], z_coords[idx]); + double min_dis_u = 1e9, min_dis_v = 1e9, min_dis_w = 1e9; + + WirePlaneId wpid = wpid_array[idx]; + int apa = wpid.apa(); + int face = wpid.face(); + + for (auto seg : existing_segments) { + auto closest_2d = segment_get_closest_2d_distances(seg, p, apa, face, "fit"); + double dis_u = std::get<0>(closest_2d); + double dis_v = std::get<1>(closest_2d); + double dis_w = std::get<2>(closest_2d); + + if (dis_u < min_dis_u) min_dis_u = dis_u; + if (dis_v < min_dis_v) min_dis_v = dis_v; + if (dis_w < min_dis_w) min_dis_w = dis_w; + } + + auto p_raw = transform->backward(p, cluster_t0, face, apa); + + int flag_num = 0; + if (min_dis_u > scaling_2d * search_range && + !cluster.grouping()->get_closest_dead_chs(p_raw, 1, apa, face, 0)) flag_num++; + if (min_dis_v > scaling_2d * search_range && + !cluster.grouping()->get_closest_dead_chs(p_raw, 1, apa, face, 1)) flag_num++; + if (min_dis_w > scaling_2d * search_range && + !cluster.grouping()->get_closest_dead_chs(p_raw, 1, apa, face, 2)) flag_num++; + + if (flag_num >= 2) temp_segments[*it].number_not_faked++; + + if (min_dis_u > temp_segments[*it].max_dis_u && + !cluster.grouping()->get_closest_dead_chs(p_raw, 1, apa, face, 0)) + temp_segments[*it].max_dis_u = min_dis_u; + if (min_dis_v > temp_segments[*it].max_dis_v && + !cluster.grouping()->get_closest_dead_chs(p_raw, 1, apa, face, 1)) + temp_segments[*it].max_dis_v = min_dis_v; + if (min_dis_w > temp_segments[*it].max_dis_w && + !cluster.grouping()->get_closest_dead_chs(p_raw, 1, apa, face, 2)) + temp_segments[*it].max_dis_w = min_dis_w; + } + + // Apply quality cuts again + if ((temp_segments[*it].number_points == 1) || + (temp_segments[*it].number_not_faked == 0 && + ((temp_segments[*it].length < 3.5 * units::cm) || ( + ((temp_segments[*it].number_not_faked < 0.25 * temp_segments[*it].number_points) || + (temp_segments[*it].number_not_faked < 0.4 * temp_segments[*it].number_points && + temp_segments[*it].length < 7 * units::cm)) && + temp_segments[*it].max_dis_u / units::cm < 3 && + temp_segments[*it].max_dis_v / units::cm < 3 && + temp_segments[*it].max_dis_w / units::cm < 3 && + temp_segments[*it].max_dis_u + temp_segments[*it].max_dis_v + + temp_segments[*it].max_dis_w < 6 * units::cm)))) { + tmp_del_set.insert(*it); + } + } + + for (auto it = tmp_del_set.begin(); it != tmp_del_set.end(); it++) { + remaining_segments.erase(*it); + } + } + + // Step 10: Perform tracking on new segments + if (!new_segments_for_tracking.empty()) { + for (auto seg : new_segments_for_tracking) { + track_fitter.add_segment(seg); + } + track_fitter.do_multi_tracking(true, true, true); + + // Optionally break long segments if requested + if (flag_break_track) { + std::vector segments_to_break(new_segments_for_tracking.begin(), + new_segments_for_tracking.end()); + break_segments(graph, track_fitter, dv, segments_to_break); + } + } +} From c26940dccdfa2f4f7a555d4aca6f7ed7c0054bb7 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sun, 7 Dec 2025 07:25:14 -0800 Subject: [PATCH 046/111] implement crawl_segment --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 2 + clus/src/NeutrinoStructureExaminer.cxx | 248 +++++++++++++++++++- 2 files changed, 249 insertions(+), 1 deletion(-) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 6aade6c6..5026c924 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -49,5 +49,7 @@ namespace WireCell::Clus::PR { // identify other segments giving the graph ... void find_other_segments(Graph& graph, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv , bool flag_break_track =true, double search_range = 1.5*units::cm, double scaling_2d = 0.8); + + bool crawl_segment(Graph& graph, Facade::Cluster& cluster, SegmentPtr seg, VertexPtr vertex, TrackFitting& track_fitter, IDetectorVolumes::pointer dv ); }; } diff --git a/clus/src/NeutrinoStructureExaminer.cxx b/clus/src/NeutrinoStructureExaminer.cxx index 771a7118..852de61b 100644 --- a/clus/src/NeutrinoStructureExaminer.cxx +++ b/clus/src/NeutrinoStructureExaminer.cxx @@ -637,4 +637,250 @@ bool PatternAlgorithms::examine_structure_4(VertexPtr vertex, bool flag_final_ve } return flag_update; -} \ No newline at end of file +} + + +bool PatternAlgorithms::crawl_segment(Graph& graph, Facade::Cluster& cluster, SegmentPtr seg, VertexPtr vertex, TrackFitting& track_fitter, IDetectorVolumes::pointer dv ){ + bool flag = false; + + // Validate that segment, vertex, and cluster all match + if (!seg || !vertex || seg->cluster() != &cluster || vertex->cluster() != &cluster) { + return flag; + } + + // Step 1: Find points at ~3cm distance from vertex on other connected segments + std::map map_segment_point; + + if (!vertex->descriptor_valid()) return flag; + auto vd = vertex->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + + for (auto eit = edge_range.first; eit != edge_range.second; ++eit) { + SegmentPtr sg = graph[*eit].segment; + if (!sg || sg == seg) continue; + + const auto& fits = sg->fits(); + if (fits.empty()) continue; + + Facade::geo_point_t min_point = fits.front().point; + double min_dis = 1e9; + + for (size_t i = 0; i < fits.size(); i++) { + double dis = std::fabs(ray_length(Ray{fits[i].point, vertex->fit().point}) - 3.0 * units::cm); + if (dis < min_dis) { + min_dis = dis; + min_point = fits[i].point; + } + } + map_segment_point[sg] = min_point; + } + + // Step 2: Determine which end of seg connects to vertex + const auto& seg_wcpts = seg->wcpts(); + const auto& seg_fits = seg->fits(); + if (seg_wcpts.size() < 2) return flag; + + bool flag_start = false; + double dis_front = ray_length(Ray{vertex->wcpt().point, seg_wcpts.front().point}); + double dis_back = ray_length(Ray{vertex->wcpt().point, seg_wcpts.back().point}); + + if (dis_front < dis_back) { + flag_start = true; + } + + // Step 3: Build list of points to test (from vertex end, excluding endpoints) + std::vector pts_to_be_tested; + + if (flag_start) { + for (size_t i = 1; i + 1 < seg_fits.size(); i++) { + pts_to_be_tested.push_back(seg_fits[i].point); + } + } else { + for (int i = int(seg_fits.size()) - 2; i > 0; i--) { + pts_to_be_tested.push_back(seg_fits[i].point); + } + } + + if (pts_to_be_tested.empty()) return flag; + + // Step 4: Test points for good connectivity + double step_size = 0.3 * units::cm; + int max_bin = -1; + + const auto transform = track_fitter.get_pc_transforms()->pc_transform( + cluster.get_scope_transform(cluster.get_default_scope())); + double cluster_t0 = cluster.get_cluster_t0(); + + for (size_t i = 0; i < pts_to_be_tested.size(); i++) { + int n_bad = 0; + Facade::geo_point_t end_p = pts_to_be_tested[i]; + + for (auto it = map_segment_point.begin(); it != map_segment_point.end(); it++) { + Facade::geo_point_t start_p = it->second; + double distance = ray_length(Ray{start_p, end_p}); + int ncount = std::round(distance / step_size); + + for (int j = 1; j < ncount; j++) { + Facade::geo_point_t test_p( + start_p.x() + (end_p.x() - start_p.x()) / ncount * j, + start_p.y() + (end_p.y() - start_p.y()) / ncount * j, + start_p.z() + (end_p.z() - start_p.z()) / ncount * j + ); + + // Check if point is good + auto test_wpid = dv->contained_by(test_p); + if (test_wpid.face() != -1 && test_wpid.apa() != -1) { + auto temp_p_raw = transform->backward(test_p, cluster_t0, test_wpid.face(), test_wpid.apa()); + if (!cluster.grouping()->is_good_point(temp_p_raw, test_wpid.apa(), test_wpid.face(), 0.2 * units::cm, 0, 0)) { + n_bad++; + } + } + } + } + + if (n_bad == 0) max_bin = i; + } + + // Step 5: Update segment and vertex if good point found + const auto& steiner_pc = cluster.get_pc("steiner_pc"); + const auto& coords = cluster.get_default_scope().coords; + const auto& x_coords = steiner_pc.get(coords.at(0))->elements(); + const auto& y_coords = steiner_pc.get(coords.at(1))->elements(); + const auto& z_coords = steiner_pc.get(coords.at(2))->elements(); + + while (max_bin >= 0) { + // Find closest steiner point to the test point + auto vtx_new_knn = cluster.kd_steiner_knn(1, pts_to_be_tested[max_bin], "steiner_pc"); + if (vtx_new_knn.empty()) { + max_bin--; + continue; + } + + size_t new_idx = vtx_new_knn[0].first; + Facade::geo_point_t vtx_new_point(x_coords[new_idx], y_coords[new_idx], z_coords[new_idx]); + + // Check if new point is valid (not the same as current endpoints) + if (flag_start && ray_length(Ray{vtx_new_point, seg_wcpts.back().point}) < 0.01 * units::cm) { + max_bin--; + continue; + } + if (!flag_start && ray_length(Ray{vtx_new_point, seg_wcpts.front().point}) < 0.01 * units::cm) { + max_bin--; + continue; + } + if (ray_length(Ray{vtx_new_point, vertex->wcpt().point}) < 0.01 * units::cm) { + break; + } + + // Update current segment + std::vector new_path; + if (flag_start) { + // Keep points from back to new vertex + double dis_limit = ray_length(Ray{vtx_new_point, seg_wcpts.back().point}); + for (int idx = seg_wcpts.size() - 1; idx >= 0; idx--) { + double dis = ray_length(Ray{seg_wcpts[idx].point, seg_wcpts.back().point}); + if (dis < dis_limit) { + new_path.push_back(seg_wcpts[idx].point); + } + } + std::reverse(new_path.begin(), new_path.end()); + if (new_path.size() > 1 && ray_length(Ray{new_path.front(), vtx_new_point}) < 0.01 * units::cm) { + new_path.erase(new_path.begin()); + } + new_path.insert(new_path.begin(), vtx_new_point); + } else { + // Keep points from front to new vertex + double dis_limit = ray_length(Ray{vtx_new_point, seg_wcpts.front().point}); + for (size_t idx = 0; idx < seg_wcpts.size(); idx++) { + double dis = ray_length(Ray{seg_wcpts[idx].point, seg_wcpts.front().point}); + if (dis < dis_limit) { + new_path.push_back(seg_wcpts[idx].point); + } + } + if (new_path.size() > 1 && ray_length(Ray{new_path.back(), vtx_new_point}) < 0.01 * units::cm) { + new_path.pop_back(); + } + new_path.push_back(vtx_new_point); + } + + // Replace segment with updated path + auto other_vertex = find_other_vertex(graph, seg, vertex); + if (!other_vertex) break; + + remove_segment(graph, seg); + auto new_seg = create_segment_for_cluster(cluster, dv, new_path, 0); + if (!new_seg) break; + + add_segment(graph, new_seg, flag_start ? other_vertex : vertex, + flag_start ? vertex : other_vertex); + seg = new_seg; + + // Update other connected segments + for (auto it = map_segment_point.begin(); it != map_segment_point.end(); it++) { + SegmentPtr other_sg = it->first; + const auto& other_wcpts = other_sg->wcpts(); + if (other_wcpts.empty()) continue; + + bool flag_front = (ray_length(Ray{other_wcpts.front().point, vertex->wcpt().point}) < + ray_length(Ray{other_wcpts.back().point, vertex->wcpt().point})); + Facade::geo_point_t min_p = it->second; + + // Find closest point in other segment to min_p + size_t min_idx = 0; + double min_dis = 1e9; + for (size_t j = 0; j < other_wcpts.size(); j++) { + double dis = ray_length(Ray{min_p, other_wcpts[j].point}); + if (dis < min_dis) { + min_dis = dis; + min_idx = j; + } + } + + // Build new path from vtx_new_point to min point + Facade::geo_point_t min_wcpt_point = other_wcpts[min_idx].point; + auto path_points = do_rough_path(cluster, vtx_new_point, min_wcpt_point); + + // Combine with rest of segment + std::vector combined_path; + if (flag_front) { + combined_path = path_points; + for (size_t j = min_idx + 1; j < other_wcpts.size(); j++) { + combined_path.push_back(other_wcpts[j].point); + } + } else { + for (size_t j = 0; j < min_idx; j++) { + combined_path.push_back(other_wcpts[j].point); + } + std::reverse(path_points.begin(), path_points.end()); + combined_path.insert(combined_path.end(), path_points.begin(), path_points.end()); + } + + if (combined_path.size() <= 1) continue; + + // Replace other segment + auto other_v2 = find_other_vertex(graph, other_sg, vertex); + if (!other_v2) continue; + + remove_segment(graph, other_sg); + auto new_other_seg = create_segment_for_cluster(cluster, dv, combined_path, 0); + if (!new_other_seg) continue; + + add_segment(graph, new_other_seg, flag_front ? vertex : other_v2, + flag_front ? other_v2 : vertex); + } + + // Update vertex position + vertex->wcpt().point = vtx_new_point; + if (vertex->fit().valid()) { + vertex->fit().point = vtx_new_point; + } + + // Perform multi-tracking + track_fitter.do_multi_tracking(true, true, true); + + flag = true; + break; + } + + return flag; +} From 911e3394dd125dc66a16c9deeed7232954fecd6d Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sun, 7 Dec 2025 07:35:02 -0800 Subject: [PATCH 047/111] add examine_segment --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 1 + clus/src/NeutrinoStructureExaminer.cxx | 208 ++++++++++++++++++++ 2 files changed, 209 insertions(+) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 5026c924..714b1179 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -50,6 +50,7 @@ namespace WireCell::Clus::PR { // identify other segments giving the graph ... void find_other_segments(Graph& graph, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv , bool flag_break_track =true, double search_range = 1.5*units::cm, double scaling_2d = 0.8); + void examine_segment(Graph& graph, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); bool crawl_segment(Graph& graph, Facade::Cluster& cluster, SegmentPtr seg, VertexPtr vertex, TrackFitting& track_fitter, IDetectorVolumes::pointer dv ); }; } diff --git a/clus/src/NeutrinoStructureExaminer.cxx b/clus/src/NeutrinoStructureExaminer.cxx index 852de61b..01008773 100644 --- a/clus/src/NeutrinoStructureExaminer.cxx +++ b/clus/src/NeutrinoStructureExaminer.cxx @@ -884,3 +884,211 @@ bool PatternAlgorithms::crawl_segment(Graph& graph, Facade::Cluster& cluster, Se return flag; } + +void PatternAlgorithms::examine_segment(Graph& graph, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv){ + // Step 1: Examine short segments with multiple connections at both ends + auto [ebegin, eend] = boost::edges(graph); + std::vector segments_to_examine; + + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr sg = graph[*eit].segment; + if (!sg || sg->cluster() != &cluster) continue; + + double length = segment_track_length(sg); + if (length > 4 * units::cm) continue; + + auto [v1, v2] = find_vertices(graph, sg); + if (!v1 || !v2) continue; + + // Check both vertices have at least 2 connections + auto v1d = v1->get_descriptor(); + auto v2d = v2->get_descriptor(); + if (boost::degree(v1d, graph) < 2 || boost::degree(v2d, graph) < 2) continue; + + segments_to_examine.push_back(sg); + } + + // Examine each short segment for potential crawling + for (auto sg : segments_to_examine) { + auto [v1, v2] = find_vertices(graph, sg); + if (!v1 || !v2) continue; + + std::vector cand_vertices = {v1, v2}; + + for (size_t i = 0; i < 2; i++) { + VertexPtr vtx = cand_vertices[i]; + if (!vtx->descriptor_valid()) continue; + + // Calculate direction of current segment at this vertex + Facade::geo_point_t vtx_point = vtx->fit().valid() ? vtx->fit().point : vtx->wcpt().point; + auto dir1 = segment_cal_dir_3vector(sg, vtx_point, 2 * units::cm); + + double max_angle = 0; + double min_angle = 180; + + // Check angles with other connected segments + auto vd = vtx->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + + for (auto eit = edge_range.first; eit != edge_range.second; ++eit) { + SegmentPtr sg1 = graph[*eit].segment; + if (!sg1 || sg1 == sg) continue; + + auto dir3 = segment_cal_dir_3vector(sg1, vtx_point, 2 * units::cm); + + // Calculate angle between directions + double dot_product = dir1.dot(dir3); + double mag1 = dir1.magnitude(); + double mag3 = dir3.magnitude(); + + if (mag1 > 0 && mag3 > 0) { + double cos_angle = dot_product / (mag1 * mag3); + // Clamp to [-1, 1] to handle numerical errors + if (cos_angle > 1.0) cos_angle = 1.0; + if (cos_angle < -1.0) cos_angle = -1.0; + double angle = std::acos(cos_angle) / 3.1415926 * 180.0; + + if (angle > max_angle) max_angle = angle; + if (angle < min_angle) min_angle = angle; + } + } + + // If angles indicate a sharp turn, try to crawl the segment + if (max_angle > 150 && min_angle > 105) { + crawl_segment(graph, cluster, sg, vtx, track_fitter, dv); + } + } + } + + // Step 2: Merge vertices at the same position + std::set all_vertices; + auto [vbegin, vend] = boost::vertices(graph); + for (auto vit = vbegin; vit != vend; ++vit) { + VertexPtr vtx = graph[*vit].vertex; + if (vtx && vtx->cluster() == &cluster) { + all_vertices.insert(vtx); + } + } + + bool flag_merge = true; + while (flag_merge) { + flag_merge = false; + VertexPtr vtx1 = nullptr; + VertexPtr vtx2 = nullptr; + + for (auto it1 = all_vertices.begin(); it1 != all_vertices.end(); ++it1) { + vtx1 = *it1; + for (auto it2 = it1; it2 != all_vertices.end(); ++it2) { + vtx2 = *it2; + + // Check if two different vertices are at the same position + if (vtx1 != vtx2 && + ray_length(Ray{vtx1->wcpt().point, vtx2->wcpt().point}) < 0.01 * units::cm) { + + // Find segments to remove (connected to both vertices) + std::vector to_be_removed_segments; + + if (vtx2->descriptor_valid()) { + auto v2d = vtx2->get_descriptor(); + auto edge_range = boost::out_edges(v2d, graph); + + for (auto eit = edge_range.first; eit != edge_range.second; ++eit) { + SegmentPtr sg = graph[*eit].segment; + if (!sg) continue; + + // Check if this segment is also connected to vtx1 + auto [seg_v1, seg_v2] = find_vertices(graph, sg); + if ((seg_v1 == vtx1 || seg_v2 == vtx1) && + (seg_v1 == vtx2 || seg_v2 == vtx2)) { + to_be_removed_segments.push_back(sg); + } + } + } + + // Merge vtx2 into vtx1 + if (merge_vertex_into_another(graph, vtx2, vtx1, dv)) { + // Remove duplicate segments + for (auto sg : to_be_removed_segments) { + remove_segment(graph, sg); + } + + all_vertices.erase(vtx2); + flag_merge = true; + break; + } + } + } + if (flag_merge) break; + } + } + + // Step 3: Remove duplicate segments (same endpoints) + std::set segments_to_be_removed; + + for (auto it = all_vertices.begin(); it != all_vertices.end(); ++it) { + VertexPtr vtx = *it; + if (!vtx->descriptor_valid()) continue; + + auto vd = vtx->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + + std::vector tmp_segments; + for (auto eit = edge_range.first; eit != edge_range.second; ++eit) { + SegmentPtr sg = graph[*eit].segment; + if (sg) tmp_segments.push_back(sg); + } + + // Compare all pairs of segments + for (size_t i = 0; i < tmp_segments.size(); i++) { + auto [v1_i, v2_i] = find_vertices(graph, tmp_segments[i]); + if (!v1_i || !v2_i) continue; + + for (size_t j = i + 1; j < tmp_segments.size(); j++) { + auto [v1_j, v2_j] = find_vertices(graph, tmp_segments[j]); + if (!v1_j || !v2_j) continue; + + // Check if segments have the same endpoints + bool same_endpoints = false; + + double dis_v1i_v1j = ray_length(Ray{v1_i->wcpt().point, v1_j->wcpt().point}); + double dis_v1i_v2j = ray_length(Ray{v1_i->wcpt().point, v2_j->wcpt().point}); + double dis_v2i_v1j = ray_length(Ray{v2_i->wcpt().point, v1_j->wcpt().point}); + double dis_v2i_v2j = ray_length(Ray{v2_i->wcpt().point, v2_j->wcpt().point}); + + if ((dis_v1i_v1j < 0.01 * units::cm && dis_v2i_v2j < 0.01 * units::cm) || + (dis_v1i_v2j < 0.01 * units::cm && dis_v2i_v1j < 0.01 * units::cm)) { + same_endpoints = true; + } + + if (same_endpoints) { + segments_to_be_removed.insert(tmp_segments[j]); + } + } + } + } + + // Remove duplicate segments + for (auto sg : segments_to_be_removed) { + remove_segment(graph, sg); + } + + // Step 4: Remove isolated vertices (no connections) + std::set vertices_to_be_removed; + auto [vbegin2, vend2] = boost::vertices(graph); + + for (auto vit = vbegin2; vit != vend2; ++vit) { + VertexPtr vtx = graph[*vit].vertex; + if (vtx && vtx->cluster() == &cluster) { + if (vtx->descriptor_valid()) { + auto vd = vtx->get_descriptor(); + if (boost::degree(vd, graph) == 0) { + vertices_to_be_removed.insert(vtx); + } + } + } + } + + for (auto vtx : vertices_to_be_removed) { + remove_vertex(graph, vtx); + } +} From 367cb7328037cf5d5fd61393db6176db790dd771 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sun, 7 Dec 2025 08:03:48 -0800 Subject: [PATCH 048/111] implement the examine_vertices_1p --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 4 + clus/src/NeutrinoStructureExaminer.cxx | 194 ++++++++++++++++++++ 2 files changed, 198 insertions(+) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 714b1179..786231fd 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -50,7 +50,11 @@ namespace WireCell::Clus::PR { // identify other segments giving the graph ... void find_other_segments(Graph& graph, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv , bool flag_break_track =true, double search_range = 1.5*units::cm, double scaling_2d = 0.8); + // examine segment void examine_segment(Graph& graph, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); bool crawl_segment(Graph& graph, Facade::Cluster& cluster, SegmentPtr seg, VertexPtr vertex, TrackFitting& track_fitter, IDetectorVolumes::pointer dv ); + + //examine vertices + bool examine_vertices_1p(Graph&graph, VertexPtr v1, VertexPtr v2, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); }; } diff --git a/clus/src/NeutrinoStructureExaminer.cxx b/clus/src/NeutrinoStructureExaminer.cxx index 01008773..3706f56a 100644 --- a/clus/src/NeutrinoStructureExaminer.cxx +++ b/clus/src/NeutrinoStructureExaminer.cxx @@ -1092,3 +1092,197 @@ void PatternAlgorithms::examine_segment(Graph& graph, Facade::Cluster& cluster, remove_vertex(graph, vtx); } } + + +bool PatternAlgorithms::examine_vertices_1p(Graph&graph, VertexPtr v1, VertexPtr v2, TrackFitting& track_fitter, IDetectorVolumes::pointer dv){ + if (!v1 || !v2 || !v1->cluster() || v1->cluster() != v2->cluster()) { + return false; + } + + auto& cluster = *v1->cluster(); + + // Find the segment between v1 and v2 + SegmentPtr sg = find_segment(graph, v1, v2); + if (!sg) return false; + + // Check that v1 has exactly 2 connections + if (!v1->descriptor_valid()) return false; + auto v1d = v1->get_descriptor(); + if (boost::degree(v1d, graph) != 2) return false; + + // Get vertex positions + Facade::geo_point_t v1_p = v1->fit().valid() ? v1->fit().point : v1->wcpt().point; + Facade::geo_point_t v2_p = v2->fit().valid() ? v2->fit().point : v2->wcpt().point; + + // Get wpid for coordinate conversion + auto v1_wpid = dv->contained_by(v1_p); + auto v2_wpid = dv->contained_by(v2_p); + if (v1_wpid.face() == -1 || v1_wpid.apa() == -1 || + v2_wpid.face() == -1 || v2_wpid.apa() == -1) { + return false; + } + + int v1_apa = v1_wpid.apa(); + int v1_face = v1_wpid.face(); + + int v2_apa = v2_wpid.apa(); + int v2_face = v2_wpid.face(); + + // Get time normalization factor from first blob + auto first_blob = cluster.children()[0]; + int ntime_ticks = first_blob->slice_index_max() - first_blob->slice_index_min(); + if (ntime_ticks <= 0) ntime_ticks = 1; // avoid division by zero + + // Convert vertices to U/V/W/T coordinates + auto [v1_t_raw, v1_u_ch] = cluster.grouping()->convert_3Dpoint_time_ch(v1_p, v1_apa, v1_face, 0); + auto [v1_t_u, v1_v_ch] = cluster.grouping()->convert_3Dpoint_time_ch(v1_p, v1_apa, v1_face, 1); + auto [v1_t_v, v1_w_ch] = cluster.grouping()->convert_3Dpoint_time_ch(v1_p, v1_apa, v1_face, 2); + + auto [v2_t_raw, v2_u_ch] = cluster.grouping()->convert_3Dpoint_time_ch(v2_p, v2_apa, v2_face, 0); + auto [v2_t_u, v2_v_ch] = cluster.grouping()->convert_3Dpoint_time_ch(v2_p, v2_apa, v2_face, 1); + auto [v2_t_v, v2_w_ch] = cluster.grouping()->convert_3Dpoint_time_ch(v2_p, v2_apa, v2_face, 2); + + // Normalize time by ntime_ticks to account for convention difference + double v1_t = double(v1_t_raw) / ntime_ticks; + double v2_t = double(v2_t_raw) / ntime_ticks; + + double v1_u = v1_u_ch; + double v1_v = v1_v_ch; + double v1_w = v1_w_ch; + + double v2_u = v2_u_ch; + double v2_v = v2_v_ch; + double v2_w = v2_w_ch; + + const auto transform = track_fitter.get_pc_transforms()->pc_transform( + cluster.get_scope_transform(cluster.get_default_scope())); + double cluster_t0 = cluster.get_cluster_t0(); + + int ncount_close = 0; + int ncount_dead = 0; + int ncount_line = 0; + + // Check each plane (U, V, W) + for (int pind = 0; pind < 3; pind++) { + double v1_wire, v2_wire; + if (pind == 0) { v1_wire = v1_u; v2_wire = v2_u; } + else if (pind == 1) { v1_wire = v1_v; v2_wire = v2_v; } + else { v1_wire = v1_w; v2_wire = v2_w; } + + // Check if vertices are close in this 2D projection + double dist_2d = std::sqrt(std::pow(v1_wire - v2_wire, 2) + std::pow(v1_t - v2_t, 2)); + + if (dist_2d < 2.5) { + ncount_close++; + } else { + // Check if segment points are all in dead region + bool flag_dead = true; + const auto& seg_fits = sg->fits(); + + for (size_t i = 0; i < seg_fits.size(); i++) { + auto test_wpid = dv->contained_by(seg_fits[i].point); + auto p_raw = transform->backward(seg_fits[i].point, cluster_t0, test_wpid.face(), test_wpid.apa()); + if (!cluster.grouping()->get_closest_dead_chs(p_raw, 1, test_wpid.apa(), test_wpid.face(), pind)) { + flag_dead = false; + break; + } + } + + if (flag_dead) { + ncount_dead++; + } else { + // Check if the third view forms a line + // Find the other segment connected to v1 + SegmentPtr sg1 = nullptr; + auto edge_range = boost::out_edges(v1d, graph); + for (auto eit = edge_range.first; eit != edge_range.second; ++eit) { + SegmentPtr temp_sg = graph[*eit].segment; + if (temp_sg && temp_sg != sg) { + sg1 = temp_sg; + break; + } + } + + if (!sg1) continue; + + const auto& pts_2 = sg1->fits(); + + // Direction vector from v1 to v2 in this 2D projection + Facade::geo_vector_t v1_2d(v2_wire - v1_wire, v2_t - v1_t, 0); + Facade::geo_vector_t v2_2d(0, 0, 0); + double min_dis = 1e9; + Facade::geo_point_t start_p = v2_p; + Facade::geo_point_t end_p; + + // Find point on sg1 that's approximately 9 units away from v1 + for (size_t i = 0; i < pts_2.size(); i++) { + auto test_wpid = dv->contained_by(pts_2[i].point); + auto [p_t_raw, p_wire_ch] = cluster.grouping()->convert_3Dpoint_time_ch(pts_2[i].point, test_wpid.apa(), test_wpid.face(), pind); + double p_t = double(p_t_raw) / ntime_ticks; + double p_wire = p_wire_ch; + + Facade::geo_vector_t v3(p_wire - v1_wire, p_t - v1_t, 0); + double dis = std::fabs(v3.magnitude() - 9.0); + if (dis < min_dis) { + min_dis = dis; + v2_2d = v3; + end_p = pts_2[i].point; + } + } + + // Check angle between v1_2d and v2_2d + double mag1 = v1_2d.magnitude(); + double mag2 = v2_2d.magnitude(); + double angle = 180.0; + + if (mag1 > 0 && mag2 > 0) { + double cos_angle = v1_2d.dot(v2_2d) / (mag1 * mag2); + if (cos_angle > 1.0) cos_angle = 1.0; + if (cos_angle < -1.0) cos_angle = -1.0; + angle = 180.0 - std::acos(cos_angle) / 3.1415926 * 180.0; + } + + if (angle < 30.0 || (mag1 < 8.0 && angle < 35.0)) { + ncount_line++; + } else { + // Check if path from v2 to end_p is good + double step_size = 0.6 * units::cm; + double path_length = ray_length(Ray{start_p, end_p}); + int ncount = std::round(path_length / step_size); + int n_bad = 0; + + for (int i = 1; i < ncount; i++) { + Facade::geo_point_t test_p( + start_p.x() + (end_p.x() - start_p.x()) / ncount * i, + start_p.y() + (end_p.y() - start_p.y()) / ncount * i, + start_p.z() + (end_p.z() - start_p.z()) / ncount * i + ); + + auto test_wpid = dv->contained_by(test_p); + if (test_wpid.face() != -1 && test_wpid.apa() != -1) { + auto temp_p_raw = transform->backward(test_p, cluster_t0, test_wpid.face(), test_wpid.apa()); + if (!cluster.grouping()->is_good_point(temp_p_raw, test_wpid.apa(), test_wpid.face(), 0.2 * units::cm, 0, 0)) { + n_bad++; + } + } + } + + if (n_bad <= 1) { + ncount_line++; + } + } + } + } + } + + // Decision logic + if (ncount_close >= 2 || + (ncount_close == 1 && ncount_dead == 1 && ncount_line >= 1) || + (ncount_close == 1 && ncount_dead == 2) || + (ncount_close == 1 && ncount_line >= 2) || + ncount_line >= 3) { + return true; + } + + return false; +} From 1ee2b3b0990f67ea962f003251a3f22386939518 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sun, 7 Dec 2025 08:15:49 -0800 Subject: [PATCH 049/111] implement examine_Vertices_1 --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 1 + clus/src/NeutrinoStructureExaminer.cxx | 106 ++++++++++++++++++++ 2 files changed, 107 insertions(+) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 786231fd..f8c36e3d 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -55,6 +55,7 @@ namespace WireCell::Clus::PR { bool crawl_segment(Graph& graph, Facade::Cluster& cluster, SegmentPtr seg, VertexPtr vertex, TrackFitting& track_fitter, IDetectorVolumes::pointer dv ); //examine vertices + bool examine_vertices_1(Graph&graph, Facade::Cluster&cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); bool examine_vertices_1p(Graph&graph, VertexPtr v1, VertexPtr v2, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); }; } diff --git a/clus/src/NeutrinoStructureExaminer.cxx b/clus/src/NeutrinoStructureExaminer.cxx index 3706f56a..e8682014 100644 --- a/clus/src/NeutrinoStructureExaminer.cxx +++ b/clus/src/NeutrinoStructureExaminer.cxx @@ -1286,3 +1286,109 @@ bool PatternAlgorithms::examine_vertices_1p(Graph&graph, VertexPtr v1, VertexPtr return false; } + +bool PatternAlgorithms::examine_vertices_1(Graph&graph, Facade::Cluster&cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv){ + bool flag_continue = false; + + VertexPtr v1 = nullptr; + VertexPtr v2 = nullptr; + VertexPtr v3 = nullptr; + + // Iterate through all vertices in the graph + auto [vbegin, vend] = boost::vertices(graph); + for (auto vit = vbegin; vit != vend; ++vit) { + VertexPtr vtx = graph[*vit].vertex; + if (!vtx || vtx->cluster() != &cluster) continue; + + // Check if vertex has exactly 2 connections (potential candidate) + if (boost::degree(*vit, graph) != 2) continue; + + // Get the two connected segments + std::vector connected_segments; + auto [ebegin, eend] = boost::out_edges(*vit, graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr seg = graph[*eit].segment; + if (seg) connected_segments.push_back(seg); + } + + if (connected_segments.size() != 2) continue; + + // Check each segment + for (auto seg : connected_segments) { + // Only consider short segments (<4cm) + if (segment_track_length(seg) > 4.0 * units::cm) continue; + + // Find the other vertex of this segment + VertexPtr vtx1 = find_other_vertex(graph, seg, vtx); + if (!vtx1) continue; + + // The other vertex must have at least 2 connections + if (!vtx1->descriptor_valid()) continue; + auto vd1 = vtx1->get_descriptor(); + if (boost::degree(vd1, graph) < 2) continue; + + // Check if these two vertices represent the same physical point + if (examine_vertices_1p(graph, vtx, vtx1, track_fitter, dv)) { + v1 = vtx; + v2 = vtx1; + + // Find v3: the vertex on the other side of v1 + for (auto seg2 : connected_segments) { + if (seg2 == seg) continue; + VertexPtr vtx_other = find_other_vertex(graph, seg2, v1); + if (vtx_other) { + v3 = vtx_other; + break; + } + } + + flag_continue = true; + break; + } + } + + if (flag_continue) break; + } + + // Merge vertices if found + if (v1 && v2 && v3) { + // Find the segments to be removed + SegmentPtr sg = find_segment(graph, v1, v2); // segment between v1 and v2 + SegmentPtr sg1 = find_segment(graph, v1, v3); // segment between v1 and v3 + + if (!sg || !sg1) { + return false; + } + + // Create new segment from v3 to v2 using Steiner graph shortest path + auto path_points = do_rough_path(cluster, v3->wcpt().point, v2->wcpt().point); + + if (path_points.size() < 2) { + return false; + } + + // Create the new segment + auto sg2 = create_segment_for_cluster(cluster, dv, path_points, 0); + if (!sg2) { + return false; + } + + std::cout << "Cluster: " << cluster.ident() << " Merge Vertices Type I " + << "combining two segments into new segment" << std::endl; + + // Add new segment to graph + add_segment(graph, sg2, v2, v3); + + // Remove old segments and vertex + remove_segment(graph, sg); + remove_segment(graph, sg1); + remove_vertex(graph, v1); + + // Update tracking + track_fitter.do_multi_tracking(true, true, true); + } + + return flag_continue; +} + + From a1f3781bf77ef453b06fd38b7f66ee46b65db062 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sun, 7 Dec 2025 08:28:40 -0800 Subject: [PATCH 050/111] implement examine_vertices_2 --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 1 + clus/src/NeutrinoStructureExaminer.cxx | 122 ++++++++++++++++++++ 2 files changed, 123 insertions(+) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index f8c36e3d..d2ed916c 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -57,5 +57,6 @@ namespace WireCell::Clus::PR { //examine vertices bool examine_vertices_1(Graph&graph, Facade::Cluster&cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); bool examine_vertices_1p(Graph&graph, VertexPtr v1, VertexPtr v2, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); + bool examine_vertices_2(Graph&graph, Facade::Cluster&cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); }; } diff --git a/clus/src/NeutrinoStructureExaminer.cxx b/clus/src/NeutrinoStructureExaminer.cxx index e8682014..ec23dc7f 100644 --- a/clus/src/NeutrinoStructureExaminer.cxx +++ b/clus/src/NeutrinoStructureExaminer.cxx @@ -1391,4 +1391,126 @@ bool PatternAlgorithms::examine_vertices_1(Graph&graph, Facade::Cluster&cluster, return flag_continue; } +bool PatternAlgorithms::examine_vertices_2(Graph&graph, Facade::Cluster&cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv){ + bool flag_continue = false; + + VertexPtr v1 = nullptr; + VertexPtr v2 = nullptr; + SegmentPtr sg = nullptr; + + // Iterate through all edges (segments) in the graph + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr segment = graph[*eit].segment; + if (!segment || segment->cluster() != &cluster) continue; + + // Get the two vertices of this segment + auto vertices = find_vertices(graph, segment); + VertexPtr vtx1 = vertices.first; + VertexPtr vtx2 = vertices.second; + + if (!vtx1 || !vtx2) continue; + + // Get positions (prefer fit point over wcpt) + Facade::geo_point_t p1 = vtx1->fit().valid() ? vtx1->fit().point : vtx1->wcpt().point; + Facade::geo_point_t p2 = vtx2->fit().valid() ? vtx2->fit().point : vtx2->wcpt().point; + + // Calculate distance between vertices + double dis = ray_length(Ray{p1, p2}); + + // Check if vertices are very close (<0.45cm) or moderately close (<1.5cm with both having degree 2) + if (dis < 0.45 * units::cm) { + v1 = vtx1; + v2 = vtx2; + sg = segment; + flag_continue = true; + break; + } else if (dis < 1.5 * units::cm) { + // Check if both vertices have exactly 2 connections + if (!vtx1->descriptor_valid() || !vtx2->descriptor_valid()) continue; + auto vd1 = vtx1->get_descriptor(); + auto vd2 = vtx2->get_descriptor(); + + if (boost::degree(vd1, graph) == 2 && boost::degree(vd2, graph) == 2) { + v1 = vtx1; + v2 = vtx2; + sg = segment; + flag_continue = true; + break; + } + } + } + + // Merge vertices if found + if (v1 && v2 && sg) { + std::cout << "Cluster: " << cluster.ident() << " Merge Vertices Type II" << std::endl; + + // Remove the segment between v1 and v2 + remove_segment(graph, sg); + + // Collect all segments connected to v2 (excluding the one we just removed) + std::vector v2_segments; + if (v2->descriptor_valid()) { + auto vd2 = v2->get_descriptor(); + auto [ebegin2, eend2] = boost::out_edges(vd2, graph); + for (auto eit2 = ebegin2; eit2 != eend2; ++eit2) { + SegmentPtr seg2 = graph[*eit2].segment; + if (seg2) { + v2_segments.push_back(seg2); + } + } + } + + // For each segment connected to v2, create a new segment from v3 to v1 + for (auto old_seg : v2_segments) { + // Find the other vertex (v3) connected to v2 through this segment + VertexPtr v3 = find_other_vertex(graph, old_seg, v2); + if (!v3) continue; + + // Create new segment from v3 to v1 using Steiner graph shortest path + auto path_points = do_rough_path(cluster, v3->wcpt().point, v1->wcpt().point); + + if (path_points.size() < 2) continue; + + // Create the new segment + auto new_seg = create_segment_for_cluster(cluster, dv, path_points, 0); + if (!new_seg) continue; + + // Add new segment to graph + add_segment(graph, new_seg, v3, v1); + + // Remove old segment + remove_segment(graph, old_seg); + } + + // Remove v2 vertex + remove_vertex(graph, v2); + + // Clean up isolated vertices (vertices with no connections) + std::vector isolated_vertices; + auto [vbegin, vend] = boost::vertices(graph); + for (auto vit = vbegin; vit != vend; ++vit) { + if (boost::degree(*vit, graph) == 0) { + VertexPtr vtx = graph[*vit].vertex; + if (vtx) { + isolated_vertices.push_back(vtx); + } + } + } + + for (auto vtx : isolated_vertices) { + remove_vertex(graph, vtx); + } + + // Update tracking + track_fitter.do_multi_tracking(true, true, true); + } + + return flag_continue; +} + + + + + From 883b9a9bc1e6dcdfb863b2813a921c578932cf18 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sun, 7 Dec 2025 08:41:15 -0800 Subject: [PATCH 051/111] implement examine_vertices_4 --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 2 + clus/src/NeutrinoStructureExaminer.cxx | 431 ++++++++++++++++++++ 2 files changed, 433 insertions(+) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index d2ed916c..4227f484 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -58,5 +58,7 @@ namespace WireCell::Clus::PR { bool examine_vertices_1(Graph&graph, Facade::Cluster&cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); bool examine_vertices_1p(Graph&graph, VertexPtr v1, VertexPtr v2, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); bool examine_vertices_2(Graph&graph, Facade::Cluster&cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); + bool examine_vertices_4(Graph&graph, Facade::Cluster&cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); + bool examine_vertices_4p(Graph&graph, VertexPtr v1, VertexPtr v2, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); }; } diff --git a/clus/src/NeutrinoStructureExaminer.cxx b/clus/src/NeutrinoStructureExaminer.cxx index ec23dc7f..b40aa6e7 100644 --- a/clus/src/NeutrinoStructureExaminer.cxx +++ b/clus/src/NeutrinoStructureExaminer.cxx @@ -1510,6 +1510,437 @@ bool PatternAlgorithms::examine_vertices_2(Graph&graph, Facade::Cluster&cluster, } +bool PatternAlgorithms::examine_vertices_4p(Graph&graph, VertexPtr v1, VertexPtr v2, TrackFitting& track_fitter, IDetectorVolumes::pointer dv){ + // Find the segment between v1 and v2 + SegmentPtr sg1 = find_segment(graph, v1, v2); + if (!sg1) return true; + + bool flag = true; + + // Get cluster information + auto cluster = v1->cluster(); + if (!cluster) return true; + + // Get transform and grouping + const auto transform = track_fitter.get_pc_transforms()->pc_transform( + cluster->get_scope_transform(cluster->get_default_scope())); + double cluster_t0 = cluster->get_cluster_t0(); + auto grouping = cluster->grouping(); + + if (!transform || !grouping) return true; + + // Get v1 and v2 positions + Facade::geo_point_t v1_point = v1->fit().valid() ? v1->fit().point : v1->wcpt().point; + Facade::geo_point_t v2_point = v2->fit().valid() ? v2->fit().point : v2->wcpt().point; + + // Check segments of v1 with respect to v2 + if (!v1->descriptor_valid()) return true; + auto vd1 = v1->get_descriptor(); + + auto [ebegin, eend] = boost::out_edges(vd1, graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr sg = graph[*eit].segment; + if (!sg || sg == sg1) continue; + + // Get segment points + const auto& pts = sg->wcpts(); + if (pts.empty()) continue; + + // Find point on segment approximately 3cm from v1 + Facade::geo_point_t min_point = pts.front().point; + double min_dis = 1e9; + + for (size_t i = 0; i < pts.size(); i++) { + double dis = std::fabs(ray_length(Ray{pts[i].point, v1_point}) - 3.0 * units::cm); + if (dis < min_dis) { + min_dis = dis; + min_point = pts[i].point; + } + } + + // Test connectivity from min_point to v2 + double step_size = 0.3 * units::cm; + Facade::geo_point_t start_p = min_point; + Facade::geo_point_t end_p = v2_point; + + double distance = ray_length(Ray{start_p, end_p}); + int ncount = std::round(distance / step_size); + int n_bad = 0; + + for (int i = 1; i < ncount; i++) { + Facade::geo_point_t test_p( + start_p.x() + (end_p.x() - start_p.x()) / ncount * i, + start_p.y() + (end_p.y() - start_p.y()) / ncount * i, + start_p.z() + (end_p.z() - start_p.z()) / ncount * i + ); + + // Check if test point is in good region + auto test_wpid = dv->contained_by(test_p); + if (test_wpid.face() != -1 && test_wpid.apa() != -1) { + auto temp_p_raw = transform->backward(test_p, cluster_t0, test_wpid.face(), test_wpid.apa()); + if (!grouping->is_good_point(temp_p_raw, test_wpid.apa(), test_wpid.face(), 0.2 * units::cm, 0, 0)) { + n_bad++; + } + } + } + + // If any bad points found, return false + if (n_bad != 0) { + flag = false; + break; + } + } + + return flag; +} + +bool PatternAlgorithms::examine_vertices_4(Graph&graph, Facade::Cluster&cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv){ + bool flag_continue = false; + + // Drift direction (X direction) + Facade::geo_vector_t drift_dir_abs(1, 0, 0); + + // Get steiner point cloud for later use + const auto& steiner_pc = cluster.get_pc("steiner_pc"); + const auto& coords = cluster.get_default_scope().coords; + const auto& x_coords = steiner_pc.get(coords.at(0))->elements(); + const auto& y_coords = steiner_pc.get(coords.at(1))->elements(); + const auto& z_coords = steiner_pc.get(coords.at(2))->elements(); + + // Iterate through all segments in the graph + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr sg = graph[*eit].segment; + if (!sg || sg->cluster() != &cluster) continue; + + const auto& pts = sg->wcpts(); + if (pts.size() < 2) continue; + + // Get vertices + auto pair_vertices = find_vertices(graph, sg); + VertexPtr v1 = pair_vertices.first; + VertexPtr v2 = pair_vertices.second; + if (!v1 || !v2) continue; + + // Calculate segment direction + Facade::geo_vector_t tmp_dir( + pts.front().point.x() - pts.back().point.x(), + pts.front().point.y() - pts.back().point.y(), + pts.front().point.z() - pts.back().point.z() + ); + + // Calculate direct length between endpoints + double direct_length = ray_length(Ray{pts.front().point, pts.back().point}); + // double track_length = segment_track_length(sg); + + // Calculate angle with drift direction (in degrees) + double tmp_dir_mag = tmp_dir.magnitude(); + double angle = 90.0; // default perpendicular + if (tmp_dir_mag > 0) { + double cos_angle = drift_dir_abs.dot(tmp_dir) / tmp_dir_mag; + // Clamp to [-1, 1] to avoid numerical issues with acos + cos_angle = std::max(-1.0, std::min(1.0, cos_angle)); + angle = std::acos(cos_angle) * 180.0 / M_PI; + } + + // Check conditions: short segment OR perpendicular to drift + if (direct_length < 2.0 * units::cm || + (tmp_dir.magnitude() < 3.5 * units::cm && std::fabs(angle - 90.0) < 10)) { + + // Check v1 first + if (!v1->descriptor_valid() || !v2->descriptor_valid()) continue; + auto vd1 = v1->get_descriptor(); + auto vd2 = v2->get_descriptor(); + + if (boost::degree(vd1, graph) >= 2 && examine_vertices_4p(graph, v1, v2, track_fitter, dv)) { + // Merge v1's segments to v2 + + // Get v2 position + Facade::geo_point_t vtx_new_point = v2->wcpt().point; + + // Collect segments connected to v1 (except sg) + std::vector v1_segments; + auto [e1begin, e1end] = boost::out_edges(vd1, graph); + for (auto e1it = e1begin; e1it != e1end; ++e1it) { + SegmentPtr sg1 = graph[*e1it].segment; + if (sg1 && sg1 != sg) { + v1_segments.push_back(sg1); + } + } + + // Process each segment connected to v1 + for (auto sg1 : v1_segments) { + const auto& vec_wcps = sg1->wcpts(); + if (vec_wcps.empty()) continue; + + // Determine which end connects to v1 + bool flag_front = (ray_length(Ray{vec_wcps.front().point, v1->wcpt().point}) < + ray_length(Ray{vec_wcps.back().point, v1->wcpt().point})); + + // Get v1 position + Facade::geo_point_t v1_point = v1->fit().valid() ? v1->fit().point : v1->wcpt().point; + + // Find point ~3cm from v1 on this segment + WCPoint min_wcp = vec_wcps.front(); + double min_dis = 1e9; + + // Calculate max distance to determine dis_cut + double max_dis = std::max( + ray_length(Ray{vec_wcps.front().point, v1_point}), + ray_length(Ray{vec_wcps.back().point, v1_point}) + ); + double dis_cut = 0; + double default_dis_cut = 2.5 * units::cm; + if (max_dis > 2 * default_dis_cut) dis_cut = default_dis_cut; + + for (size_t j = 0; j < vec_wcps.size(); j++) { + double dis1 = ray_length(Ray{vec_wcps[j].point, v1_point}); + double dis = std::fabs(dis1 - 3.0 * units::cm); + if (dis < min_dis && dis1 > dis_cut) { + min_wcp = vec_wcps[j]; + min_dis = dis; + } + } + + // Build new path from v2 to min_wcp using Steiner graph + std::list new_list; + new_list.push_back(v2->wcpt()); + + // Add intermediate points + double dis_step = 2.0 * units::cm; + int ncount = std::round(ray_length(Ray{vtx_new_point, min_wcp.point}) / dis_step); + if (ncount < 2) ncount = 2; + + for (int qx = 1; qx < ncount; qx++) { + Facade::geo_point_t tmp_p( + vtx_new_point.x() + (min_wcp.point.x() - vtx_new_point.x()) / ncount * qx, + vtx_new_point.y() + (min_wcp.point.y() - vtx_new_point.y()) / ncount * qx, + vtx_new_point.z() + (min_wcp.point.z() - vtx_new_point.z()) / ncount * qx + ); + + // Find closest steiner point + auto knn_results = cluster.kd_steiner_knn(1, tmp_p, "steiner_pc"); + if (!knn_results.empty()) { + size_t idx = knn_results[0].first; + WCPoint tmp_wcp; + tmp_wcp.point = Facade::geo_point_t(x_coords[idx], y_coords[idx], z_coords[idx]); + + double dist_to_steiner = ray_length(Ray{tmp_wcp.point, tmp_p}); + if (dist_to_steiner > 0.3 * units::cm) continue; + + // Check if not duplicate + bool is_duplicate = (ray_length(Ray{tmp_wcp.point, new_list.back().point}) < 0.01 * units::cm); + bool is_min_wcp = (ray_length(Ray{tmp_wcp.point, min_wcp.point}) < 0.01 * units::cm); + + if (!is_duplicate && !is_min_wcp) { + new_list.push_back(tmp_wcp); + } + } + } + new_list.push_back(min_wcp); + + // Combine with rest of segment + std::list old_list(vec_wcps.begin(), vec_wcps.end()); + + if (flag_front) { + // Remove points up to min_wcp from front + while (!old_list.empty() && + ray_length(Ray{old_list.front().point, min_wcp.point}) > 0.01 * units::cm) { + old_list.pop_front(); + } + if (!old_list.empty()) old_list.pop_front(); + + // Prepend new path + for (auto it = new_list.rbegin(); it != new_list.rend(); ++it) { + old_list.push_front(*it); + } + } else { + // Remove points up to min_wcp from back + while (!old_list.empty() && + ray_length(Ray{old_list.back().point, min_wcp.point}) > 0.01 * units::cm) { + old_list.pop_back(); + } + if (!old_list.empty()) old_list.pop_back(); + + // Append new path + for (auto it = new_list.rbegin(); it != new_list.rend(); ++it) { + old_list.push_back(*it); + } + } + + // Update segment with new points + std::vector new_wcpts(old_list.begin(), old_list.end()); + sg1->wcpts(new_wcpts); + + // Find other vertex and update connection + VertexPtr v3 = find_other_vertex(graph, sg1, v1); + if (v3) { + remove_segment(graph, sg1); + add_segment(graph, sg1, v2, v3); + } + } + + // Remove v1 and sg + remove_vertex(graph, v1); + remove_segment(graph, sg); + + flag_continue = true; + std::cout << "Cluster: " << cluster.ident() << " Merge Vertices Type III" << std::endl; + track_fitter.do_multi_tracking(true, true, true); + break; + + } else if (boost::degree(vd2, graph) >= 2 && examine_vertices_4p(graph, v2, v1, track_fitter, dv)) { + // Merge v2's segments to v1 (symmetric case) + + // Get v1 position + Facade::geo_point_t vtx_new_point = v1->wcpt().point; + + // Collect segments connected to v2 (except sg) + std::vector v2_segments; + auto [e2begin, e2end] = boost::out_edges(vd2, graph); + for (auto e2it = e2begin; e2it != e2end; ++e2it) { + SegmentPtr sg1 = graph[*e2it].segment; + if (sg1 && sg1 != sg) { + v2_segments.push_back(sg1); + } + } + + // Process each segment connected to v2 + for (auto sg1 : v2_segments) { + const auto& vec_wcps = sg1->wcpts(); + if (vec_wcps.empty()) continue; + + // Determine which end connects to v2 + bool flag_front = (ray_length(Ray{vec_wcps.front().point, v2->wcpt().point}) < + ray_length(Ray{vec_wcps.back().point, v2->wcpt().point})); + + // Get v2 position + Facade::geo_point_t v2_point = v2->fit().valid() ? v2->fit().point : v2->wcpt().point; + + // Find point ~3cm from v2 on this segment + WCPoint min_wcp = vec_wcps.front(); + double min_dis = 1e9; + + // Calculate max distance to determine dis_cut + double max_dis = std::max( + ray_length(Ray{vec_wcps.front().point, v2_point}), + ray_length(Ray{vec_wcps.back().point, v2_point}) + ); + double dis_cut = 0; + double default_dis_cut = 2.5 * units::cm; + if (max_dis > 2 * default_dis_cut) dis_cut = default_dis_cut; + + for (size_t j = 0; j < vec_wcps.size(); j++) { + double dis1 = ray_length(Ray{vec_wcps[j].point, v2_point}); + double dis = std::fabs(dis1 - 3.0 * units::cm); + if (dis < min_dis && dis1 > dis_cut) { + min_wcp = vec_wcps[j]; + min_dis = dis; + } + } + + // Build new path from v1 to min_wcp using Steiner graph + std::list new_list; + new_list.push_back(v1->wcpt()); + + // Add intermediate points + double dis_step = 2.0 * units::cm; + int ncount = std::round(ray_length(Ray{vtx_new_point, min_wcp.point}) / dis_step); + if (ncount < 2) ncount = 2; + + for (int qx = 1; qx < ncount; qx++) { + Facade::geo_point_t tmp_p( + vtx_new_point.x() + (min_wcp.point.x() - vtx_new_point.x()) / ncount * qx, + vtx_new_point.y() + (min_wcp.point.y() - vtx_new_point.y()) / ncount * qx, + vtx_new_point.z() + (min_wcp.point.z() - vtx_new_point.z()) / ncount * qx + ); + + // Find closest steiner point + auto knn_results = cluster.kd_steiner_knn(1, tmp_p, "steiner_pc"); + if (!knn_results.empty()) { + size_t idx = knn_results[0].first; + WCPoint tmp_wcp; + tmp_wcp.point = Facade::geo_point_t(x_coords[idx], y_coords[idx], z_coords[idx]); + + double dist_to_steiner = ray_length(Ray{tmp_wcp.point, tmp_p}); + if (dist_to_steiner > 0.3 * units::cm) continue; + + // Check if not duplicate + bool is_duplicate = (ray_length(Ray{tmp_wcp.point, new_list.back().point}) < 0.01 * units::cm); + bool is_min_wcp = (ray_length(Ray{tmp_wcp.point, min_wcp.point}) < 0.01 * units::cm); + + if (!is_duplicate && !is_min_wcp) { + new_list.push_back(tmp_wcp); + } + } + } + new_list.push_back(min_wcp); + + // Combine with rest of segment + std::list old_list(vec_wcps.begin(), vec_wcps.end()); + + if (flag_front) { + // Remove points up to min_wcp from front + while (!old_list.empty() && + ray_length(Ray{old_list.front().point, min_wcp.point}) > 0.01 * units::cm) { + old_list.pop_front(); + } + if (!old_list.empty()) old_list.pop_front(); + + // Prepend new path + for (auto it = new_list.rbegin(); it != new_list.rend(); ++it) { + old_list.push_front(*it); + } + } else { + // Remove points up to min_wcp from back + while (!old_list.empty() && + ray_length(Ray{old_list.back().point, min_wcp.point}) > 0.01 * units::cm) { + old_list.pop_back(); + } + if (!old_list.empty()) old_list.pop_back(); + + // Append new path + for (auto it = new_list.rbegin(); it != new_list.rend(); ++it) { + old_list.push_back(*it); + } + } + + // Update segment with new points + std::vector new_wcpts(old_list.begin(), old_list.end()); + sg1->wcpts(new_wcpts); + + // Find other vertex and update connection + VertexPtr v3 = find_other_vertex(graph, sg1, v2); + if (v3) { + remove_segment(graph, sg1); + add_segment(graph, sg1, v1, v3); + } + } + + // Remove v2 and sg + remove_vertex(graph, v2); + remove_segment(graph, sg); + + flag_continue = true; + std::cout << "Cluster: " << cluster.ident() << " Merge Vertices Type III" << std::endl; + track_fitter.do_multi_tracking(true, true, true); + break; + } + } + + if (flag_continue) break; + } + + return flag_continue; +} + + + + + + + + From 5305889d0922c498f3d25caf6601fe4665e41544 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sun, 7 Dec 2025 08:43:49 -0800 Subject: [PATCH 052/111] implement examine_vertices --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 1 + clus/src/NeutrinoStructureExaminer.cxx | 25 +++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 4227f484..f8167cba 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -55,6 +55,7 @@ namespace WireCell::Clus::PR { bool crawl_segment(Graph& graph, Facade::Cluster& cluster, SegmentPtr seg, VertexPtr vertex, TrackFitting& track_fitter, IDetectorVolumes::pointer dv ); //examine vertices + void examine_vertices(Graph& graph, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); bool examine_vertices_1(Graph&graph, Facade::Cluster&cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); bool examine_vertices_1p(Graph&graph, VertexPtr v1, VertexPtr v2, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); bool examine_vertices_2(Graph&graph, Facade::Cluster&cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); diff --git a/clus/src/NeutrinoStructureExaminer.cxx b/clus/src/NeutrinoStructureExaminer.cxx index b40aa6e7..193ad529 100644 --- a/clus/src/NeutrinoStructureExaminer.cxx +++ b/clus/src/NeutrinoStructureExaminer.cxx @@ -1934,6 +1934,31 @@ bool PatternAlgorithms::examine_vertices_4(Graph&graph, Facade::Cluster&cluster, return flag_continue; } +void PatternAlgorithms::examine_vertices(Graph& graph, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv){ + bool flag_continue = true; + + while (flag_continue) { + flag_continue = false; + + // Examine and clean up segment topology + examine_segment(graph, cluster, track_fitter, dv); + + // Merge vertex if the kink is not at right location (Type I) + flag_continue = flag_continue || examine_vertices_1(graph, cluster, track_fitter, dv); + + // Count vertices in the graph + size_t num_vertices = boost::num_vertices(graph); + + // Merge vertices if they are too close (Type II) - only if more than 2 vertices + if (num_vertices > 2) { + flag_continue = flag_continue || examine_vertices_2(graph, cluster, track_fitter, dv); + } + + // Merge vertices if they are reasonably close (Type III/IV) + flag_continue = flag_continue || examine_vertices_4(graph, cluster, track_fitter, dv); + } +} + From d82f18089b3ad29664dfed2b5659da2223a64a2c Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sun, 7 Dec 2025 15:16:07 -0800 Subject: [PATCH 053/111] implement the examine partial identical segments --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 2 + clus/src/NeutrinoStructureExaminer.cxx | 243 ++++++++++++++++++++ 2 files changed, 245 insertions(+) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index f8167cba..695d2877 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -53,6 +53,7 @@ namespace WireCell::Clus::PR { // examine segment void examine_segment(Graph& graph, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); bool crawl_segment(Graph& graph, Facade::Cluster& cluster, SegmentPtr seg, VertexPtr vertex, TrackFitting& track_fitter, IDetectorVolumes::pointer dv ); + void examine_partial_identical_segments(Graph& graph, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); //examine vertices void examine_vertices(Graph& graph, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); @@ -61,5 +62,6 @@ namespace WireCell::Clus::PR { bool examine_vertices_2(Graph&graph, Facade::Cluster&cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); bool examine_vertices_4(Graph&graph, Facade::Cluster&cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); bool examine_vertices_4p(Graph&graph, VertexPtr v1, VertexPtr v2, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); + }; } diff --git a/clus/src/NeutrinoStructureExaminer.cxx b/clus/src/NeutrinoStructureExaminer.cxx index 193ad529..5d894dea 100644 --- a/clus/src/NeutrinoStructureExaminer.cxx +++ b/clus/src/NeutrinoStructureExaminer.cxx @@ -1959,6 +1959,249 @@ void PatternAlgorithms::examine_vertices(Graph& graph, Facade::Cluster& cluster, } } +void PatternAlgorithms::examine_partial_identical_segments(Graph& graph, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv){ + bool flag_continue = true; + + // Get steiner point cloud + const auto& steiner_pc = cluster.get_pc("steiner_pc"); + const auto& coords = cluster.get_default_scope().coords; + const auto& x_coords = steiner_pc.get(coords.at(0))->elements(); + const auto& y_coords = steiner_pc.get(coords.at(1))->elements(); + const auto& z_coords = steiner_pc.get(coords.at(2))->elements(); + + while (flag_continue) { + flag_continue = false; + + // Iterate through all vertices + auto [vbegin, vend] = boost::vertices(graph); + for (auto vit = vbegin; vit != vend; ++vit) { + VertexPtr vtx = graph[*vit].vertex; + if (!vtx || vtx->cluster() != &cluster) continue; + + // Only process vertices with more than 2 connections + size_t degree = boost::degree(*vit, graph); + if (degree <= 2) continue; + + // Collect all segments connected to this vertex + std::vector connected_segments; + auto [ebegin, eend] = boost::out_edges(*vit, graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr seg = graph[*eit].segment; + if (seg) connected_segments.push_back(seg); + } + + // Find pair of segments with maximum overlap distance + SegmentPtr max_sg1 = nullptr; + SegmentPtr max_sg2 = nullptr; + double max_dis = 0; + Facade::geo_point_t max_point; + + for (size_t i = 0; i < connected_segments.size(); i++) { + SegmentPtr sg1 = connected_segments[i]; + const auto& pts_1 = sg1->wcpts(); + if (pts_1.empty()) continue; + + // Order points from vertex outward + std::vector test_pts; + bool front_is_vtx = (ray_length(Ray{pts_1.front().point, vtx->wcpt().point}) < + ray_length(Ray{pts_1.back().point, vtx->wcpt().point})); + + if (front_is_vtx) { + for (const auto& pt : pts_1) { + test_pts.push_back(pt.point); + } + } else { + for (auto it = pts_1.rbegin(); it != pts_1.rend(); ++it) { + test_pts.push_back(it->point); + } + } + + // Compare with other segments + for (size_t j = i + 1; j < connected_segments.size(); j++) { + SegmentPtr sg2 = connected_segments[j]; + + // Check overlap along sg1 + for (size_t k = 0; k < test_pts.size(); k++) { + auto [closest_dis, closest_pt] = segment_get_closest_point(sg2, test_pts[k], "fit"); + + if (closest_dis < 0.3 * units::cm) { + // Get position for vertex + Facade::geo_point_t vtx_point = vtx->fit().valid() ? vtx->fit().point : vtx->wcpt().point; + double dis = ray_length(Ray{test_pts[k], vtx_point}); + + if (dis > max_dis) { + max_dis = dis; + max_point = test_pts[k]; + max_sg1 = sg1; + max_sg2 = sg2; + } + } else { + break; // No longer overlapping + } + } + } + } + + // If significant overlap found (>5cm), split the vertex + if (max_dis > 5.0 * units::cm) { + // Find closest existing vertex to max_point + double min_dis = 1e9; + VertexPtr min_vertex = nullptr; + + auto [vbegin2, vend2] = boost::vertices(graph); + for (auto vit2 = vbegin2; vit2 != vend2; ++vit2) { + VertexPtr vtx1 = graph[*vit2].vertex; + if (!vtx1 || vtx1->cluster() != &cluster) continue; + + Facade::geo_point_t vtx1_point = vtx1->fit().valid() ? vtx1->fit().point : vtx1->wcpt().point; + double dis = ray_length(Ray{max_point, vtx1_point}); + + if (dis < min_dis) { + min_dis = dis; + min_vertex = vtx1; + } + } + + if (min_dis < 0.3 * units::cm) { + // Merge to existing vertex + + // Check if there's already a segment between min_vertex and vtx + SegmentPtr good_segment = find_segment(graph, min_vertex, vtx); + + // If no existing segment, create one + if (!good_segment) { + auto path_points = do_rough_path(cluster, min_vertex->wcpt().point, vtx->wcpt().point); + if (path_points.size() >= 2) { + auto sg3 = create_segment_for_cluster(cluster, dv, path_points, 0); + if (sg3) { + add_segment(graph, sg3, min_vertex, vtx); + } + } + } + + // Reconnect max_sg1 to min_vertex + if (max_sg1 && max_sg1 != good_segment) { + VertexPtr tmp_vtx = find_other_vertex(graph, max_sg1, vtx); + if (tmp_vtx && tmp_vtx != min_vertex) { + auto path_points = do_rough_path(cluster, min_vertex->wcpt().point, tmp_vtx->wcpt().point); + if (path_points.size() >= 2) { + std::vector new_wcpts; + for (const auto& p : path_points) { + WCPoint wcp; + wcp.point = p; + new_wcpts.push_back(wcp); + } + max_sg1->wcpts(new_wcpts); + + remove_segment(graph, max_sg1); + add_segment(graph, max_sg1, min_vertex, tmp_vtx); + } + } else if (tmp_vtx == min_vertex) { + remove_segment(graph, max_sg1); + } + } + + // Reconnect max_sg2 to min_vertex + if (max_sg2 && max_sg2 != good_segment) { + VertexPtr tmp_vtx = find_other_vertex(graph, max_sg2, vtx); + if (tmp_vtx && tmp_vtx != min_vertex) { + auto path_points = do_rough_path(cluster, min_vertex->wcpt().point, tmp_vtx->wcpt().point); + if (path_points.size() >= 2) { + std::vector new_wcpts; + for (const auto& p : path_points) { + WCPoint wcp; + wcp.point = p; + new_wcpts.push_back(wcp); + } + max_sg2->wcpts(new_wcpts); + + remove_segment(graph, max_sg2); + add_segment(graph, max_sg2, min_vertex, tmp_vtx); + } + } else if (tmp_vtx == min_vertex) { + remove_segment(graph, max_sg2); + } + } + + track_fitter.do_multi_tracking(true, true, true); + + } else { + // Create new vertex at split point + + // Find closest steiner point + auto knn_results = cluster.kd_steiner_knn(1, max_point, "steiner_pc"); + if (!knn_results.empty()) { + size_t idx = knn_results[0].first; + Facade::geo_point_t vtx_new_point(x_coords[idx], y_coords[idx], z_coords[idx]); + + // Create new vertex + auto vtx2 = make_vertex(graph); + WCPoint new_wcp; + new_wcp.point = vtx_new_point; + vtx2->wcpt(new_wcp).cluster(&cluster); + + // Create segment between new vertex and original vertex + auto path_points = do_rough_path(cluster, vtx_new_point, vtx->wcpt().point); + if (path_points.size() >= 2) { + auto sg3 = create_segment_for_cluster(cluster, dv, path_points, 0); + if (sg3) { + add_segment(graph, sg3, vtx2, vtx); + } + } + + // Reconnect max_sg1 to new vertex + if (max_sg1) { + VertexPtr tmp_vtx = find_other_vertex(graph, max_sg1, vtx); + if (tmp_vtx) { + auto path_points1 = do_rough_path(cluster, vtx_new_point, tmp_vtx->wcpt().point); + if (path_points1.size() >= 2) { + std::vector new_wcpts; + for (const auto& p : path_points1) { + WCPoint wcp; + wcp.point = p; + new_wcpts.push_back(wcp); + } + max_sg1->wcpts(new_wcpts); + + remove_segment(graph, max_sg1); + add_segment(graph, max_sg1, vtx2, tmp_vtx); + } + } + } + + // Reconnect max_sg2 to new vertex + if (max_sg2) { + VertexPtr tmp_vtx = find_other_vertex(graph, max_sg2, vtx); + if (tmp_vtx) { + auto path_points2 = do_rough_path(cluster, vtx_new_point, tmp_vtx->wcpt().point); + if (path_points2.size() >= 2) { + std::vector new_wcpts; + for (const auto& p : path_points2) { + WCPoint wcp; + wcp.point = p; + new_wcpts.push_back(wcp); + } + max_sg2->wcpts(new_wcpts); + + remove_segment(graph, max_sg2); + add_segment(graph, max_sg2, vtx2, tmp_vtx); + } + } + } + + track_fitter.do_multi_tracking(true, true, true); + } + } + + flag_continue = true; + } + + if (flag_continue) break; + } + } +} + + From 28fc7db106dbd199e313ff363e49683f9c6ec186 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sun, 7 Dec 2025 15:24:26 -0800 Subject: [PATCH 054/111] get_local_extension --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 2 + clus/src/NeutrinoStructureExaminer.cxx | 58 +++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 695d2877..2fa1815f 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -62,6 +62,8 @@ namespace WireCell::Clus::PR { bool examine_vertices_2(Graph&graph, Facade::Cluster&cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); bool examine_vertices_4(Graph&graph, Facade::Cluster&cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); bool examine_vertices_4p(Graph&graph, VertexPtr v1, VertexPtr v2, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); + Facade::geo_point_t get_local_extension(Facade::Cluster& cluster, Facade::geo_point_t& wcp); + }; } diff --git a/clus/src/NeutrinoStructureExaminer.cxx b/clus/src/NeutrinoStructureExaminer.cxx index 5d894dea..dceec11d 100644 --- a/clus/src/NeutrinoStructureExaminer.cxx +++ b/clus/src/NeutrinoStructureExaminer.cxx @@ -2201,6 +2201,64 @@ void PatternAlgorithms::examine_partial_identical_segments(Graph& graph, Facade: } } +Facade::geo_point_t PatternAlgorithms::get_local_extension(Facade::Cluster& cluster, Facade::geo_point_t& wcp){ + // Determine which point cloud to use + std::string pc_name = "steiner_pc"; + + // Get local direction using Hough transform + Facade::geo_vector_t dir1 = cluster.vhough_transform(wcp, 10.0 * units::cm); + dir1 = dir1 * (-1.0); // Reverse direction + + // Drift direction + Facade::geo_vector_t drift_dir(1, 0, 0); + + // Calculate angle with drift direction (in degrees) + double dir1_mag = dir1.magnitude(); + if (dir1_mag == 0) return wcp; + + double cos_angle = drift_dir.dot(dir1) / dir1_mag; + cos_angle = std::max(-1.0, std::min(1.0, cos_angle)); // Clamp to [-1, 1] + double angle = std::acos(cos_angle) * 180.0 / M_PI; + + // If angle is close to perpendicular to drift (90° ± 7.5°), return original point + if (std::fabs(angle - 90.0) < 7.5) { + return wcp; + } + + // Get nearby points within 10 cm radius + auto kd_results = cluster.kd_steiner_radius(10.0 * units::cm, wcp, pc_name); + + if (kd_results.empty()) { + return wcp; + } + + // Get point coordinates + const auto& pc = cluster.get_pc(pc_name); + const auto& coords = cluster.get_default_scope().coords; + const auto& x_coords = pc.get(coords.at(0))->elements(); + const auto& y_coords = pc.get(coords.at(1))->elements(); + const auto& z_coords = pc.get(coords.at(2))->elements(); + + // Find point with maximum projection along dir1 + double max_val = 0; + geo_point_t result = wcp; + + for (const auto& [idx, dist] : kd_results) { + Facade::geo_point_t pt(x_coords[idx], y_coords[idx], z_coords[idx]); + + // Calculate projection along dir1 + double val = dir1.x() * (pt.x() - wcp.x()) + + dir1.y() * (pt.y() - wcp.y()) + + dir1.z() * (pt.z() - wcp.z()); + + if (val > max_val) { + max_val = val; + result = pt; + } + } + + return result; +} From c5cabfd9c6e182f2f84d57827d8e508bf7ef4f2b Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sun, 7 Dec 2025 16:02:55 -0800 Subject: [PATCH 055/111] implement the examine_vertices_3 function --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 1 + clus/src/NeutrinoStructureExaminer.cxx | 190 ++++++++++++++++++++ 2 files changed, 191 insertions(+) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 2fa1815f..10ec6165 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -63,6 +63,7 @@ namespace WireCell::Clus::PR { bool examine_vertices_4(Graph&graph, Facade::Cluster&cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); bool examine_vertices_4p(Graph&graph, VertexPtr v1, VertexPtr v2, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); Facade::geo_point_t get_local_extension(Facade::Cluster& cluster, Facade::geo_point_t& wcp); + void examine_vertices_3(Graph& graph, Facade::Cluster& main_cluster, std::pair main_cluster_initial_pair_vertices, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); }; diff --git a/clus/src/NeutrinoStructureExaminer.cxx b/clus/src/NeutrinoStructureExaminer.cxx index dceec11d..d045831d 100644 --- a/clus/src/NeutrinoStructureExaminer.cxx +++ b/clus/src/NeutrinoStructureExaminer.cxx @@ -2260,6 +2260,196 @@ Facade::geo_point_t PatternAlgorithms::get_local_extension(Facade::Cluster& clus return result; } +void PatternAlgorithms::examine_vertices_3(Graph& graph, Facade::Cluster& main_cluster, std::pair main_cluster_initial_pair_vertices, TrackFitting& track_fitter, IDetectorVolumes::pointer dv){ + // Examine main_cluster_initial_pair_vertices to see if they need extension + std::vector temp_vertices; + if (main_cluster_initial_pair_vertices.first) temp_vertices.push_back(main_cluster_initial_pair_vertices.first); + if (main_cluster_initial_pair_vertices.second) temp_vertices.push_back(main_cluster_initial_pair_vertices.second); + + bool flag_refit = false; + + for (size_t i = 0; i < temp_vertices.size(); i++) { + VertexPtr vtx = temp_vertices[i]; + + // Check if vertex is still in graph + if (!vtx->descriptor_valid()) continue; + auto vd = vtx->get_descriptor(); + + // Only process vertices with exactly one connected segment + if (boost::degree(vd, graph) != 1) continue; + + // Get the single connected segment + SegmentPtr sg = nullptr; + auto [ebegin, eend] = boost::out_edges(vd, graph); + for (auto eit = ebegin; eit != eend; ++eit) { + sg = graph[*eit].segment; + break; + } + if (!sg) continue; + + const auto& wcps = sg->wcpts(); + if (wcps.empty()) continue; + + // Determine which end of segment connects to vertex + bool flag_start = (ray_length(Ray{wcps.front().point, vtx->wcpt().point}) < + ray_length(Ray{wcps.back().point, vtx->wcpt().point})); + + // Get the other end of the segment + WCPoint wcp2 = flag_start ? wcps.back() : wcps.front(); + + // Try to extend the vertex using get_local_extension + auto wcp1_point = get_local_extension(main_cluster, vtx->wcpt().point); + + // Check if extension found a different point + bool same_as_vtx = (ray_length(Ray{wcp1_point, vtx->wcpt().point}) < 0.01 * units::cm); + bool same_as_wcp2 = (ray_length(Ray{wcp1_point, wcp2.point}) < 0.01 * units::cm); + + if (same_as_vtx || same_as_wcp2) continue; + + // Create new path from extended point to other end + std::vector path_points; + if (flag_start) { + path_points = do_rough_path(main_cluster, wcp1_point, wcp2.point); + } else { + path_points = do_rough_path(main_cluster, wcp2.point, wcp1_point); + } + + // Only update if new path is not too much longer than original + if (path_points.size() > 0 && path_points.size() < wcps.size() * 2) { + // Update vertex position + vtx->wcpt().point = wcp1_point; + + // Update segment path + std::vector new_wcpts; + for (const auto& p : path_points) { + WCPoint wcp; + wcp.point = p; + new_wcpts.push_back(wcp); + } + sg->wcpts(new_wcpts); + + flag_refit = true; + } + } + + if (flag_refit) { + track_fitter.do_multi_tracking(true, true, true); + } + + // Find and remove redundant short segments + std::set segments_to_be_removed; + + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr sg = graph[*eit].segment; + if (!sg || sg->cluster() != &main_cluster) continue; + + auto pair_vertices = find_vertices(graph, sg); + if (!pair_vertices.first || !pair_vertices.second) continue; + + // Check if either vertex has only one connection + auto vd1 = pair_vertices.first->get_descriptor(); + auto vd2 = pair_vertices.second->get_descriptor(); + bool v1_single = (boost::degree(vd1, graph) == 1); + bool v2_single = (boost::degree(vd2, graph) == 1); + + if (!v1_single && !v2_single) continue; + + // Check if segment is short + double direct_length = segment_track_direct_length(sg); + if (direct_length >= 5.0 * units::cm) continue; + + // Check if all points on this segment are close to other segments in 2D + const auto& pts = sg->wcpts(); + int num_unique = 0; + + for (size_t i = 0; i < pts.size(); i++) { + // Get APA and face for this point + auto wpid = dv->contained_by(pts[i].point); + if (wpid.apa() == -1 || wpid.face() == -1) continue; + + double min_u = 1e9; + double min_v = 1e9; + double min_w = 1e9; + + // Compare with all other segments + auto [e2begin, e2end] = boost::edges(graph); + for (auto e2it = e2begin; e2it != e2end; ++e2it) { + SegmentPtr sg1 = graph[*e2it].segment; + if (!sg1 || sg1 == sg) continue; + + auto [dist_u, dist_v, dist_w] = segment_get_closest_2d_distances(sg1, pts[i].point, wpid.apa(), wpid.face(), "fit"); + + if (dist_u < min_u) min_u = dist_u; + if (dist_v < min_v) min_v = dist_v; + if (dist_w < min_w) min_w = dist_w; + } + + // If point is far from all other segments in any view, it's unique + if (min_u > 0.6 * units::cm || min_v > 0.6 * units::cm || min_w > 0.6 * units::cm) { + num_unique++; + } + } + + // If no unique points, mark for removal + if (num_unique == 0) { + segments_to_be_removed.insert(sg); + } + } + + // Collect vertices that will need examination after removal + std::set can_vertices; + + for (auto sg : segments_to_be_removed) { + auto pair_vertices = find_vertices(graph, sg); + + if (pair_vertices.first && pair_vertices.first->descriptor_valid()) { + auto vd1 = pair_vertices.first->get_descriptor(); + if (boost::degree(vd1, graph) > 1) { + can_vertices.insert(pair_vertices.first); + } + } + + if (pair_vertices.second && pair_vertices.second->descriptor_valid()) { + auto vd2 = pair_vertices.second->get_descriptor(); + if (boost::degree(vd2, graph) > 1) { + can_vertices.insert(pair_vertices.second); + } + } + + remove_segment(graph, sg); + } + + // Remove isolated vertices + bool flag_cont = true; + while (flag_cont) { + flag_cont = false; + + auto [vbegin, vend] = boost::vertices(graph); + for (auto vit = vbegin; vit != vend; ++vit) { + if (boost::degree(*vit, graph) == 0) { + VertexPtr vtx = graph[*vit].vertex; + if (vtx) { + remove_vertex(graph, vtx); + flag_cont = true; + break; + } + } + } + } + + // Examine remaining vertices with examine_structure_4 + for (auto vtx : can_vertices) { + if (vtx->descriptor_valid()) { + examine_structure_4(vtx, false, graph, main_cluster, track_fitter, dv); + } + } + + // Refit if segments were removed + if (segments_to_be_removed.size() > 0) { + track_fitter.do_multi_tracking(true, true, true); + } +} From 88c95a70aba5e439d0ebd2fbe449a231977cdc62 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sun, 7 Dec 2025 16:15:05 -0800 Subject: [PATCH 056/111] implement find_proto_vertex --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 3 + clus/src/NeutrinoPatternBase.cxx | 67 +++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 10ec6165..019f9fab 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -64,6 +64,9 @@ namespace WireCell::Clus::PR { bool examine_vertices_4p(Graph&graph, VertexPtr v1, VertexPtr v2, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); Facade::geo_point_t get_local_extension(Facade::Cluster& cluster, Facade::geo_point_t& wcp); void examine_vertices_3(Graph& graph, Facade::Cluster& main_cluster, std::pair main_cluster_initial_pair_vertices, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); + + // master pattern recognition function + bool find_proto_vertex(Graph& graph, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, bool flag_break_track = true, int nrounds_find_other_tracks = 2, bool flag_back_search = true); }; diff --git a/clus/src/NeutrinoPatternBase.cxx b/clus/src/NeutrinoPatternBase.cxx index a6b2d61c..75aed201 100644 --- a/clus/src/NeutrinoPatternBase.cxx +++ b/clus/src/NeutrinoPatternBase.cxx @@ -1012,3 +1012,70 @@ Facade::geo_vector_t PatternAlgorithms::vertex_segment_get_dir(VertexPtr& vertex return dir; } +bool PatternAlgorithms::find_proto_vertex(Graph& graph, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, bool flag_break_track, int nrounds_find_other_tracks, bool flag_back_search){ + // Check if steiner point cloud exists and has enough points + if (!cluster.has_pc("steiner_pc")) return false; + + const auto& steiner_pc = cluster.get_pc("steiner_pc"); + if (steiner_pc.size() < 2) return false; + + // Initialize first segment + SegmentPtr sg1 = init_first_segment(graph, cluster, nullptr, track_fitter, dv, flag_back_search); + + if (!sg1) return false; + + // Store initial pair of vertices for main cluster + std::pair main_cluster_initial_pair_vertices{nullptr, nullptr}; + bool is_main_cluster = cluster.get_flag(Facade::Flags::main_cluster); + + if (is_main_cluster) { + main_cluster_initial_pair_vertices = find_vertices(graph, sg1); + } + + // Check if segment has more than one point + const auto& wcpts = sg1->wcpts(); + if (wcpts.size() <= 1) { + return false; + } + + // Break tracks and examine structure + if (flag_break_track) { + std::vector remaining_segments; + remaining_segments.push_back(sg1); + break_segments(graph, track_fitter, dv, remaining_segments); + + // Examine and improve structure + examine_structure(graph, cluster, track_fitter, dv); + } else { + // Just do multi-tracking + track_fitter.do_multi_tracking(true, true, true); + } + + // Find other segments + for (int i = 0; i < nrounds_find_other_tracks; i++) { + find_other_segments(graph, cluster, track_fitter, dv, flag_break_track); + } + + // For main cluster, merge tracks if angles are consistent + if (is_main_cluster) { + if (examine_structure_3(graph, cluster, track_fitter, dv)) { + track_fitter.do_multi_tracking(true, true, true); + } + } + + // Examine the vertices + examine_vertices(graph, cluster, track_fitter, dv); + + // Examine partial identical segments + examine_partial_identical_segments(graph, cluster, track_fitter, dv); + + // Examine the two initial points for main cluster + if (is_main_cluster && main_cluster_initial_pair_vertices.first) { + examine_vertices_3(graph, cluster, main_cluster_initial_pair_vertices, track_fitter, dv); + } + + // Final multi-tracking + track_fitter.do_multi_tracking(true, true, true); + + return true; +} \ No newline at end of file From 7c17ed844caca79262dd787cf2ebcef353aaa079 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sun, 7 Dec 2025 16:30:51 -0800 Subject: [PATCH 057/111] implement the init_point_segment --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 2 + clus/src/NeutrinoPatternBase.cxx | 67 ++++++++++++++++++++- 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 019f9fab..cd9a140e 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -13,6 +13,7 @@ namespace WireCell::Clus::PR { // find the shortest path using steiner graph std::vector do_rough_path(const Facade::Cluster& cluster,Facade::geo_point_t& first_point, Facade::geo_point_t& last_point); + std::vector do_rough_path_reg_pc(const Facade::Cluster& cluster, Facade::geo_point_t& first_point, Facade::geo_point_t& last_point, std::string graph_name = "relaxed_pid"); // create a segment given a path SegmentPtr create_segment_for_cluster(WireCell::Clus::Facade::Cluster& cluster, IDetectorVolumes::pointer dv, const std::vector& path_points, int dir = 0); // create a segment given two vertices, null, if failed @@ -68,6 +69,7 @@ namespace WireCell::Clus::PR { // master pattern recognition function bool find_proto_vertex(Graph& graph, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, bool flag_break_track = true, int nrounds_find_other_tracks = 2, bool flag_back_search = true); + void init_point_segment(Graph& graph, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); }; } diff --git a/clus/src/NeutrinoPatternBase.cxx b/clus/src/NeutrinoPatternBase.cxx index 75aed201..bc79f872 100644 --- a/clus/src/NeutrinoPatternBase.cxx +++ b/clus/src/NeutrinoPatternBase.cxx @@ -90,6 +90,33 @@ std::vector PatternAlgorithms::do_rough_path(const Facade:: return path_points; } +std::vector PatternAlgorithms::do_rough_path_reg_pc(const Facade::Cluster& cluster, Facade::geo_point_t& first_point, Facade::geo_point_t& last_point, std::string graph_name){ + // Find closest indices in the regular point cloud using kd_knn + auto first_knn_results = cluster.kd_knn(1, first_point); + auto last_knn_results = cluster.kd_knn(1, last_point); + + auto first_index = first_knn_results[0].first; // Get the index from the first result + auto last_index = last_knn_results[0].first; // Get the index from the first result + + // Use the specified graph to find the shortest path + const std::vector& path_indices = + cluster.graph_algorithms(graph_name).shortest_path(first_index, last_index); + + // Convert indices to points using the regular point cloud + std::vector path_points; + const auto& points = cluster.points(); // Returns array of coordinate arrays [x_coords, y_coords, z_coords] + const auto& x_coords = points[0]; + const auto& y_coords = points[1]; + const auto& z_coords = points[2]; + + for (size_t idx : path_indices) { + path_points.emplace_back(x_coords[idx], y_coords[idx], z_coords[idx]); + } + + return path_points; +} + + SegmentPtr PatternAlgorithms::create_segment_for_cluster(WireCell::Clus::Facade::Cluster& cluster, IDetectorVolumes::pointer dv, const std::vector& path_points, int dir){ // Step 3: Prepare segment data std::vector wcpoints; @@ -1078,4 +1105,42 @@ bool PatternAlgorithms::find_proto_vertex(Graph& graph, Facade::Cluster& cluster track_fitter.do_multi_tracking(true, true, true); return true; -} \ No newline at end of file +} + + +void PatternAlgorithms::init_point_segment(Graph& graph, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv) { + // Get two boundary points from the cluster (using regular point cloud) + auto boundary_wcps = cluster.get_two_boundary_wcps(false); + + // Find shortest path using the regular point cloud with "relaxed_pid" graph + auto path_points = do_rough_path_reg_pc(cluster, boundary_wcps.first, boundary_wcps.second, "relaxed_pid"); + + // Check if path has enough points + if (path_points.size() <= 1) { + return; + } + + // Create vertices for the endpoints + VertexPtr v1 = make_vertex(graph); + v1->wcpt().point = boundary_wcps.first; + v1->cluster(&cluster); + + VertexPtr v2 = make_vertex(graph); + v2->wcpt().point = boundary_wcps.second; + v2->cluster(&cluster); + + // Create segment with the path points + auto sg1 = create_segment_for_cluster(cluster, dv, path_points); + if (!sg1) { + remove_vertex(graph, v1); + remove_vertex(graph, v2); + return; + } + + // Add segment to graph connecting the two vertices + add_segment(graph, sg1, v1, v2); + + // Perform multi-tracking to fit the segment + track_fitter.add_segment(sg1); + track_fitter.do_multi_tracking(true, true, true); +} From c58c0f761c2b7227a796f0219a30082adf95ec82 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Tue, 16 Dec 2025 11:35:36 -0800 Subject: [PATCH 058/111] implement the first function of examine_structure_final_1 --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 2 + clus/src/NeutrinoStructureExaminer.cxx | 170 +++++++++++++++++++- 2 files changed, 168 insertions(+), 4 deletions(-) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index cd9a140e..39d06b58 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -71,5 +71,7 @@ namespace WireCell::Clus::PR { void init_point_segment(Graph& graph, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); + bool examine_structure_final_1(Graph& graph, VertexPtr main_vertex, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); + }; } diff --git a/clus/src/NeutrinoStructureExaminer.cxx b/clus/src/NeutrinoStructureExaminer.cxx index d045831d..5da991df 100644 --- a/clus/src/NeutrinoStructureExaminer.cxx +++ b/clus/src/NeutrinoStructureExaminer.cxx @@ -2452,10 +2452,172 @@ void PatternAlgorithms::examine_vertices_3(Graph& graph, Facade::Cluster& main_c } - - - - + +bool PatternAlgorithms::examine_structure_final_1(Graph& graph, VertexPtr main_vertex, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv) { + // Merge two segments if a direct connection is better + bool flag_update = false; + bool flag_continue = true; + + // Get transform and grouping from track_fitter + const auto transform = track_fitter.get_pc_transforms()->pc_transform( + cluster.get_scope_transform(cluster.get_default_scope())); + double cluster_t0 = cluster.get_cluster_t0(); + auto grouping = cluster.grouping(); + + if (!transform || !grouping) { + return false; + } + + while (flag_continue) { + flag_continue = false; + + // Iterate through all vertices in the graph + auto [vbegin, vend] = boost::vertices(graph); + for (auto vit = vbegin; vit != vend; ++vit) { + VertexPtr vtx = graph[*vit].vertex; + + // Skip if vertex doesn't belong to this cluster + if (!vtx || vtx->cluster() != &cluster) continue; + + // Skip the main vertex + if (vtx == main_vertex) continue; + + // Only consider vertices with exactly 2 connections + auto vd = vtx->get_descriptor(); + if (boost::degree(vd, graph) != 2) continue; + + // Get the two segments connected to this vertex + auto edge_range = boost::out_edges(vd, graph); + auto eit = edge_range.first; + SegmentPtr sg1 = graph[*eit].segment; + ++eit; + SegmentPtr sg2 = graph[*eit].segment; + + if (!sg1 || !sg2) continue; + + // Check if segments have identical endpoints (same start and end points) + const auto& wcpts1 = sg1->wcpts(); + const auto& wcpts2 = sg2->wcpts(); + + if (wcpts1.size() < 2 || wcpts2.size() < 2) continue; + + // Check if segments are identical (same endpoints) + double dist_front_front = ray_length(Ray{wcpts1.front().point, wcpts2.front().point}); + double dist_back_back = ray_length(Ray{wcpts1.back().point, wcpts2.back().point}); + double dist_front_back = ray_length(Ray{wcpts1.front().point, wcpts2.back().point}); + double dist_back_front = ray_length(Ray{wcpts1.back().point, wcpts2.front().point}); + + if ((dist_front_front < 0.1*units::cm && dist_back_back < 0.1*units::cm) || + (dist_front_back < 0.1*units::cm && dist_back_front < 0.1*units::cm)) { + // Segments are identical, delete one + remove_segment(graph, sg2); + flag_update = true; + flag_continue = true; + break; + } + + // Get segment lengths + // double length1 = segment_track_length(sg1); + // double length2 = segment_track_length(sg2); + + // Get the other vertices + VertexPtr vtx1 = find_other_vertex(graph, sg1, vtx); + VertexPtr vtx2 = find_other_vertex(graph, sg2, vtx); + + if (!vtx1 || !vtx2) continue; + + // Get start and end points (use fit if available, otherwise wcpt) + Facade::geo_point_t start_p = vtx1->fit().valid() ? vtx1->fit().point : vtx1->wcpt().point; + Facade::geo_point_t end_p = vtx2->fit().valid() ? vtx2->fit().point : vtx2->wcpt().point; + + // Check the straight line path + double step_size = 0.6 * units::cm; + double distance = ray_length(Ray{start_p, end_p}); + int ncount = std::round(distance / step_size); + + std::vector new_pts; + bool flag_replace = true; + int n_bad = 0; + + // Test points along the straight line + for (int i = 1; i < ncount; i++) { + Facade::geo_point_t test_p( + start_p.x() + (end_p.x() - start_p.x()) / ncount * i, + start_p.y() + (end_p.y() - start_p.y()) / ncount * i, + start_p.z() + (end_p.z() - start_p.z()) / ncount * i + ); + new_pts.push_back(test_p); + + // Check if this point is good + auto test_wpid = dv->contained_by(test_p); + if (test_wpid.face() != -1 && test_wpid.apa() != -1) { + auto temp_p_raw = transform->backward(test_p, cluster_t0, test_wpid.face(), test_wpid.apa()); + if (!grouping->is_good_point(temp_p_raw, test_wpid.apa(), test_wpid.face(), 0.2*units::cm, 0, 0)) { + n_bad++; + } + } + + if (n_bad > 1) { + flag_replace = false; + break; + } + } + + // If the straight line is better, replace the two segments with one new segment + if (flag_replace) { + // Build list of points for the new segment using steiner point cloud + std::list wcps_list; + + // Add start point + wcps_list.push_back(vtx1->wcpt().point); + + // Add intermediate points by finding closest points in steiner point cloud + for (size_t i = 0; i < new_pts.size(); i++) { + auto [idx, closest_pt] = cluster.get_closest_wcpoint(new_pts[i]); + + // Only add if different from the last point + if (wcps_list.empty() || ray_length(Ray{wcps_list.back(), closest_pt}) > 0.01*units::cm) { + wcps_list.push_back(closest_pt); + } + } + + // Add end point if different from last point + if (wcps_list.empty() || ray_length(Ray{wcps_list.back(), vtx2->wcpt().point}) > 0.01*units::cm) { + wcps_list.push_back(vtx2->wcpt().point); + } + + // Convert list to vector + std::vector path_points(wcps_list.begin(), wcps_list.end()); + + if (path_points.size() > 1) { + // Create new segment + SegmentPtr sg3 = create_segment_for_cluster(cluster, dv, path_points); + if (sg3) { + // Add new segment to graph + add_segment(graph, sg3, vtx1, vtx2); + + // Delete old segments + remove_segment(graph, sg1); + remove_segment(graph, sg2); + + // Delete the middle vertex + remove_vertex(graph, vtx); + + flag_update = true; + flag_continue = true; + break; + } + } + } + } + } // while continue + + if (flag_update) { + track_fitter.do_multi_tracking(true, true, true); + } + + return flag_update; +} From ffd4f50d343807f5c8e752a26db44988d1afccb3 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Tue, 16 Dec 2025 11:56:28 -0800 Subject: [PATCH 059/111] implement the examine_structure_final functions --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 5 + clus/src/NeutrinoStructureExaminer.cxx | 816 ++++++++++++++++++++ 2 files changed, 821 insertions(+) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 39d06b58..f5fb3f6b 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -71,7 +71,12 @@ namespace WireCell::Clus::PR { void init_point_segment(Graph& graph, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); + // examine the structure of the patterns ... bool examine_structure_final_1(Graph& graph, VertexPtr main_vertex, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); + bool examine_structure_final_1p(Graph& graph, VertexPtr main_vertex, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); + bool examine_structure_final_2(Graph& graph, VertexPtr main_vertex, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); + bool examine_structure_final_3(Graph& graph, VertexPtr main_vertex, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); + bool examine_structure_final(Graph& graph, VertexPtr main_vertex, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); }; } diff --git a/clus/src/NeutrinoStructureExaminer.cxx b/clus/src/NeutrinoStructureExaminer.cxx index 5da991df..edae3c69 100644 --- a/clus/src/NeutrinoStructureExaminer.cxx +++ b/clus/src/NeutrinoStructureExaminer.cxx @@ -2619,7 +2619,823 @@ bool PatternAlgorithms::examine_structure_final_1(Graph& graph, VertexPtr main_v return flag_update; } +bool PatternAlgorithms::examine_structure_final_1p(Graph& graph, VertexPtr main_vertex, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv){ + bool flag_update = false; + + // Check if main_vertex has exactly 2 connected segments + if (!main_vertex->descriptor_valid()) return flag_update; + auto vd = main_vertex->get_descriptor(); + if (boost::degree(vd, graph) != 2) return flag_update; + + // Get the two segments connected to main_vertex + auto edge_range = boost::out_edges(vd, graph); + auto eit = edge_range.first; + SegmentPtr sg1 = graph[*eit].segment; + ++eit; + SegmentPtr sg2 = graph[*eit].segment; + + if (!sg1 || !sg2) return flag_update; + + // Get main vertex position + WireCell::Point main_vtx_point = main_vertex->fit().valid() ? main_vertex->fit().point : main_vertex->wcpt().point; + + // Calculate direction vectors for both segments + WireCell::Vector dir1 = segment_cal_dir_3vector(sg1, main_vtx_point, 15*units::cm); + WireCell::Vector dir2 = segment_cal_dir_3vector(sg2, main_vtx_point, 15*units::cm); + + // Calculate angle between directions (in degrees) + double angle = (3.1415926 - std::acos(dir1.dot(dir2) / (dir1.magnitude() * dir2.magnitude()))) / 3.1415926 * 180.0; + + // Get segment lengths + double length1 = segment_track_length(sg1); + double length2 = segment_track_length(sg2); + + // Only proceed if segments are nearly collinear (angle > 175 degrees) + if (angle > 175) { + // Get transform and grouping for point validation + const auto transform = track_fitter.get_pc_transforms()->pc_transform( + cluster.get_scope_transform(cluster.get_default_scope())); + // double cluster_t0 = cluster.get_cluster_t0(); + auto grouping = cluster.grouping(); + + if (!transform || !grouping) return flag_update; + + if (length1 < 6*units::cm && length1 < length2) { + // sg1 is short - merge it into sg2 + VertexPtr vtx = find_other_vertex(graph, sg1, main_vertex); + if (!vtx) return flag_update; + + const auto& vec_wcps = sg2->wcpts(); + const auto& vec_wcps1 = sg1->wcpts(); + + if (vec_wcps.empty() || vec_wcps1.empty()) return flag_update; + + // Determine which end of sg2 connects to main_vertex + bool flag_front = (ray_length(Ray{vec_wcps.front().point, main_vtx_point}) < 0.01*units::cm); + + // Determine which end of sg1 connects to main_vertex + bool flag_front1 = (ray_length(Ray{vec_wcps1.front().point, main_vtx_point}) < 0.01*units::cm); + + // Create a list to merge the wcpts + std::list old_list; + std::copy(vec_wcps.begin(), vec_wcps.end(), std::back_inserter(old_list)); + + // Merge sg1 points into sg2 based on orientation + if (flag_front && flag_front1) { + // Both connect at front - add sg1 in order to front of old_list + for (auto it1 = vec_wcps1.begin(); it1 != vec_wcps1.end(); it1++) { + if (ray_length(Ray{(*it1).point, old_list.front().point}) > 0.01*units::cm) { + old_list.push_front(*it1); + } + } + } else if (flag_front && (!flag_front1)) { + // sg2 front connects, sg1 back connects - add sg1 in reverse to front of old_list + for (auto it1 = vec_wcps1.rbegin(); it1 != vec_wcps1.rend(); it1++) { + if (ray_length(Ray{(*it1).point, old_list.front().point}) > 0.01*units::cm) { + old_list.push_front(*it1); + } + } + } else if ((!flag_front) && flag_front1) { + // sg2 back connects, sg1 front connects - add sg1 in order to back of old_list + for (auto it1 = vec_wcps1.begin(); it1 != vec_wcps1.end(); it1++) { + if (ray_length(Ray{(*it1).point, old_list.back().point}) > 0.01*units::cm) { + old_list.push_back(*it1); + } + } + } else if ((!flag_front) && (!flag_front1)) { + // Both connect at back - add sg1 in reverse to back of old_list + for (auto it1 = vec_wcps1.rbegin(); it1 != vec_wcps1.rend(); it1++) { + if (ray_length(Ray{(*it1).point, old_list.back().point}) > 0.01*units::cm) { + old_list.push_back(*it1); + } + } + } + + // Update sg2's wcpts with merged list + std::vector new_wcpts; + new_wcpts.reserve(old_list.size()); + std::copy(std::begin(old_list), std::end(old_list), std::back_inserter(new_wcpts)); + sg2->wcpts(new_wcpts); + + // Update main_vertex to vtx's position + WCPoint vtx_wcp = vtx->wcpt(); + main_vertex->wcpt(vtx_wcp); + if (vtx->fit().valid()) { + main_vertex->fit(vtx->fit()); + } + + // Reconnect all segments from vtx to main_vertex (except sg1) + std::vector vtx_segments; + if (vtx->descriptor_valid()) { + auto vtx_vd = vtx->get_descriptor(); + auto [vtx_ebegin, vtx_eend] = boost::out_edges(vtx_vd, graph); + for (auto vtx_eit = vtx_ebegin; vtx_eit != vtx_eend; ++vtx_eit) { + SegmentPtr seg = graph[*vtx_eit].segment; + if (seg && seg != sg1) { + vtx_segments.push_back(seg); + } + } + } + + for (auto seg : vtx_segments) { + VertexPtr other_vtx = find_other_vertex(graph, seg, vtx); + if (other_vtx && other_vtx != main_vertex) { + remove_segment(graph, seg); + add_segment(graph, seg, main_vertex, other_vtx); + } + } + + // Delete sg1 and vtx + remove_segment(graph, sg1); + remove_vertex(graph, vtx); + + flag_update = true; + + } else if (length2 < 6*units::cm && length2 < length1) { + // sg2 is short - merge it into sg1 + VertexPtr vtx = find_other_vertex(graph, sg2, main_vertex); + if (!vtx) return flag_update; + + const auto& vec_wcps = sg1->wcpts(); + const auto& vec_wcps1 = sg2->wcpts(); + + if (vec_wcps.empty() || vec_wcps1.empty()) return flag_update; + + // Determine which end of sg1 connects to main_vertex + bool flag_front = (ray_length(Ray{vec_wcps.front().point, main_vtx_point}) < 0.01*units::cm); + + // Determine which end of sg2 connects to main_vertex + bool flag_front1 = (ray_length(Ray{vec_wcps1.front().point, main_vtx_point}) < 0.01*units::cm); + + // Create a list to merge the wcpts + std::list old_list; + std::copy(vec_wcps.begin(), vec_wcps.end(), std::back_inserter(old_list)); + + // Merge sg2 points into sg1 based on orientation + if (flag_front && flag_front1) { + // Both connect at front - add sg2 in order to front of old_list + for (auto it1 = vec_wcps1.begin(); it1 != vec_wcps1.end(); it1++) { + if (ray_length(Ray{(*it1).point, old_list.front().point}) > 0.01*units::cm) { + old_list.push_front(*it1); + } + } + } else if (flag_front && (!flag_front1)) { + // sg1 front connects, sg2 back connects - add sg2 in reverse to front of old_list + for (auto it1 = vec_wcps1.rbegin(); it1 != vec_wcps1.rend(); it1++) { + if (ray_length(Ray{(*it1).point, old_list.front().point}) > 0.01*units::cm) { + old_list.push_front(*it1); + } + } + } else if ((!flag_front) && flag_front1) { + // sg1 back connects, sg2 front connects - add sg2 in order to back of old_list + for (auto it1 = vec_wcps1.begin(); it1 != vec_wcps1.end(); it1++) { + if (ray_length(Ray{(*it1).point, old_list.back().point}) > 0.01*units::cm) { + old_list.push_back(*it1); + } + } + } else if ((!flag_front) && (!flag_front1)) { + // Both connect at back - add sg2 in reverse to back of old_list + for (auto it1 = vec_wcps1.rbegin(); it1 != vec_wcps1.rend(); it1++) { + if (ray_length(Ray{(*it1).point, old_list.back().point}) > 0.01*units::cm) { + old_list.push_back(*it1); + } + } + } + + // Update sg1's wcpts with merged list + std::vector new_wcpts; + new_wcpts.reserve(old_list.size()); + std::copy(std::begin(old_list), std::end(old_list), std::back_inserter(new_wcpts)); + sg1->wcpts(new_wcpts); + + // Update main_vertex to vtx's position + WCPoint vtx_wcp = vtx->wcpt(); + main_vertex->wcpt(vtx_wcp); + if (vtx->fit().valid()) { + main_vertex->fit(vtx->fit()); + } + + // Reconnect all segments from vtx to main_vertex (except sg2) + std::vector vtx_segments; + if (vtx->descriptor_valid()) { + auto vtx_vd = vtx->get_descriptor(); + auto [vtx_ebegin, vtx_eend] = boost::out_edges(vtx_vd, graph); + for (auto vtx_eit = vtx_ebegin; vtx_eit != vtx_eend; ++vtx_eit) { + SegmentPtr seg = graph[*vtx_eit].segment; + if (seg && seg != sg2) { + vtx_segments.push_back(seg); + } + } + } + + for (auto seg : vtx_segments) { + VertexPtr other_vtx = find_other_vertex(graph, seg, vtx); + if (other_vtx && other_vtx != main_vertex) { + remove_segment(graph, seg); + add_segment(graph, seg, main_vertex, other_vtx); + } + } + + // Delete sg2 and vtx + remove_segment(graph, sg2); + remove_vertex(graph, vtx); + + flag_update = true; + } + + // If we updated, redo multi-tracking + if (flag_update) { + track_fitter.do_multi_tracking(true, true, true); + } + } + + return flag_update; +} +bool PatternAlgorithms::examine_structure_final_2(Graph& graph, VertexPtr main_vertex, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv) { + bool flag_updated = false; + + if (!main_vertex || !main_vertex->descriptor_valid()) return flag_updated; + + // Get transform and grouping for point validation + const auto transform = track_fitter.get_pc_transforms()->pc_transform( + cluster.get_scope_transform(cluster.get_default_scope())); + double cluster_t0 = cluster.get_cluster_t0(); + auto grouping = cluster.grouping(); + + if (!transform || !grouping) return flag_updated; + + // Continue looping until no more updates + bool flag_continue = true; + while (flag_continue) { + flag_continue = false; + bool flag_update = false; + + auto main_vd = main_vertex->get_descriptor(); + + // Loop over all segments connected to main_vertex + auto [ebegin, eend] = boost::out_edges(main_vd, graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr sg = graph[*eit].segment; + if (!sg) continue; + + // Find the other vertex of this segment + VertexPtr vtx1 = find_other_vertex(graph, sg, main_vertex); + if (!vtx1 || !vtx1->descriptor_valid()) continue; + + // Skip if either vertex has only 1 connection + auto vtx1_vd = vtx1->get_descriptor(); + if (boost::degree(vtx1_vd, graph) == 1 || boost::degree(main_vd, graph) == 1) continue; + + // Check distance between vertices + WireCell::Point main_vtx_point = main_vertex->fit().valid() ? main_vertex->fit().point : main_vertex->wcpt().point; + WireCell::Point vtx1_point = vtx1->fit().valid() ? vtx1->fit().point : vtx1->wcpt().point; + + double dis = ray_length(Ray{main_vtx_point, vtx1_point}); + + if (dis < 2.0*units::cm) { + // Check if vtx1 can be merged into main_vertex + flag_update = true; + + // Check all segments connected to vtx1 (except sg) + auto [vtx1_ebegin, vtx1_eend] = boost::out_edges(vtx1_vd, graph); + for (auto vtx1_eit = vtx1_ebegin; vtx1_eit != vtx1_eend; ++vtx1_eit) { + SegmentPtr sg1 = graph[*vtx1_eit].segment; + if (!sg1 || sg1 == sg) continue; + + const auto& pts = sg1->wcpts(); + if (pts.empty()) continue; + + // Determine which end of sg connects to vtx1 + const auto& sg_wcpts = sg->wcpts(); + if (sg_wcpts.empty()) continue; + + bool flag_start = (ray_length(Ray{sg_wcpts.front().point, vtx1_point}) < 0.01*units::cm); + + // Find point at ~3cm from vtx1 + WireCell::Point min_point = pts.front().point; + double min_dis = 1e9; + int min_index = 0; + + for (size_t i = 0; i < pts.size(); i++) { + double dis = std::fabs(ray_length(Ray{pts.at(i).point, vtx1_point}) - 3*units::cm); + if (dis < min_dis) { + min_dis = dis; + min_point = pts.at(i).point; + min_index = i; + } + } + + // Check connectivity from min_point to vtx1 + bool flag_connect = true; + if (flag_start) { + for (int i = min_index; i >= 0; i--) { + auto test_wpid = dv->contained_by(pts.at(i).point); + if (test_wpid.face() != -1 && test_wpid.apa() != -1) { + auto temp_p_raw = transform->backward(pts.at(i).point, cluster_t0, test_wpid.face(), test_wpid.apa()); + if (!grouping->is_good_point(temp_p_raw, test_wpid.apa(), test_wpid.face(), 0.2*units::cm, 0, 0)) { + flag_connect = false; + break; + } + } + } + } else { + for (size_t i = min_index; i < pts.size(); i++) { + auto test_wpid = dv->contained_by(pts.at(i).point); + if (test_wpid.face() != -1 && test_wpid.apa() != -1) { + auto temp_p_raw = transform->backward(pts.at(i).point, cluster_t0, test_wpid.face(), test_wpid.apa()); + if (!grouping->is_good_point(temp_p_raw, test_wpid.apa(), test_wpid.face(), 0.2*units::cm, 0, 0)) { + flag_connect = false; + break; + } + } + } + } + + // Check path from min_point to main_vertex + if (flag_connect) { + double step_size = 0.3 * units::cm; + WireCell::Point start_p = min_point; + WireCell::Point end_p = main_vtx_point; + int ncount = std::round(ray_length(Ray{start_p, end_p}) / step_size); + int n_bad = 0; + + for (int i = 1; i < ncount; i++) { + WireCell::Point test_p( + start_p.x() + (end_p.x() - start_p.x()) / ncount * i, + start_p.y() + (end_p.y() - start_p.y()) / ncount * i, + start_p.z() + (end_p.z() - start_p.z()) / ncount * i + ); + + auto test_wpid = dv->contained_by(test_p); + if (test_wpid.face() != -1 && test_wpid.apa() != -1) { + auto temp_p_raw = transform->backward(test_p, cluster_t0, test_wpid.face(), test_wpid.apa()); + if (!grouping->is_good_point(temp_p_raw, test_wpid.apa(), test_wpid.face(), 0.2*units::cm, 0, 0)) { + n_bad++; + } + } + } + if (n_bad > 0) flag_update = false; + } + } + + // Check if sg is solid in all three views (if vtx1 has only 2 connections) + if ((!flag_update) && boost::degree(vtx1_vd, graph) == 2) { + const auto& tmp_pts = sg->wcpts(); + for (size_t i = 0; i < tmp_pts.size(); i++) { + WireCell::Point test_p = tmp_pts.at(i).point; + + auto test_wpid = dv->contained_by(test_p); + if (test_wpid.face() != -1 && test_wpid.apa() != -1) { + auto temp_p_raw = transform->backward(test_p, cluster_t0, test_wpid.face(), test_wpid.apa()); + if (!grouping->is_good_point(temp_p_raw, test_wpid.apa(), test_wpid.face(), 0.2*units::cm, 0, 0)) { + flag_update = true; + } + } + + // Check midpoint + if (i + 1 != tmp_pts.size()) { + WireCell::Point mid_p( + test_p.x() + (tmp_pts.at(i+1).point.x() - test_p.x()) / 2., + test_p.y() + (tmp_pts.at(i+1).point.y() - test_p.y()) / 2., + test_p.z() + (tmp_pts.at(i+1).point.z() - test_p.z()) / 2. + ); + + auto mid_wpid = dv->contained_by(mid_p); + if (mid_wpid.face() != -1 && mid_wpid.apa() != -1) { + auto mid_p_raw = transform->backward(mid_p, cluster_t0, mid_wpid.face(), mid_wpid.apa()); + if (!grouping->is_good_point(mid_p_raw, mid_wpid.apa(), mid_wpid.face(), 0.2*units::cm, 0, 0)) { + flag_update = true; + } + } + } + } + } + + // Perform the merge + if (flag_update) { + std::cout << "Cluster: " << cluster.ident() << " Final stage merge vertex to main vertex " + // << vtx1->ident() << " " << vtx1_point << " " << main_vertex->ident() + << " " << main_vtx_point << std::endl; + + // Collect segments to reconnect + std::vector segments_to_reconnect; + auto [vtx1_ebegin2, vtx1_eend2] = boost::out_edges(vtx1_vd, graph); + for (auto vtx1_eit = vtx1_ebegin2; vtx1_eit != vtx1_eend2; ++vtx1_eit) { + SegmentPtr sg1 = graph[*vtx1_eit].segment; + if (sg1 && sg1 != sg) { + segments_to_reconnect.push_back(sg1); + } + } + + // Process each segment to reconnect + for (auto sg1 : segments_to_reconnect) { + WCPoint vtx_new_wcp = main_vertex->wcpt(); + std::vector vec_wcps = sg1->wcpts(); + + if (vec_wcps.empty()) continue; + + // Determine orientation + bool flag_front = (ray_length(Ray{vec_wcps.front().point, vtx1_point}) < 0.01*units::cm); + + // Find point at ~3cm from vtx1 + WCPoint min_wcp = vec_wcps.front(); + double min_dis = 1e9; + + for (size_t j = 0; j < vec_wcps.size(); j++) { + double dis1 = ray_length(Ray{vec_wcps.at(j).point, vtx1_point}); + double dis = std::fabs(dis1 - 3.0*units::cm); + if (dis < min_dis) { + min_wcp = vec_wcps.at(j); + min_dis = dis; + } + } + + // Build shortest path from main_vertex to min_wcp + std::list new_list; + new_list.push_back(vtx_new_wcp); + + // Add intermediate points using steiner point cloud + { + double dis_step = 1.0*units::cm; + int ncount = std::round(ray_length(Ray{vtx_new_wcp.point, min_wcp.point}) / dis_step); + if (ncount < 2) ncount = 2; + + for (int qx = 1; qx < ncount; qx++) { + WireCell::Point tmp_p( + vtx_new_wcp.point.x() + (min_wcp.point.x() - vtx_new_wcp.point.x()) / ncount * qx, + vtx_new_wcp.point.y() + (min_wcp.point.y() - vtx_new_wcp.point.y()) / ncount * qx, + vtx_new_wcp.point.z() + (min_wcp.point.z() - vtx_new_wcp.point.z()) / ncount * qx + ); + + auto [tmp_idx, tmp_wcp_pt] = cluster.get_closest_wcpoint(tmp_p); + WCPoint tmp_wcp; + tmp_wcp.point = tmp_wcp_pt; + + // Check distance + if (ray_length(Ray{tmp_wcp.point, tmp_p}) > 0.3*units::cm) continue; + + // Check if different from last point and min_wcp + if (ray_length(Ray{tmp_wcp.point, new_list.back().point}) > 0.01*units::cm && + ray_length(Ray{tmp_wcp.point, min_wcp.point}) > 0.01*units::cm) { + new_list.push_back(tmp_wcp); + } + } + } + new_list.push_back(min_wcp); + + // Merge with existing wcpts + std::list old_list; + std::copy(vec_wcps.begin(), vec_wcps.end(), std::back_inserter(old_list)); + + if (flag_front) { + // Remove points up to min_wcp from front + while (old_list.size() > 0 && ray_length(Ray{old_list.front().point, min_wcp.point}) > 0.01*units::cm) { + old_list.pop_front(); + } + if (old_list.size() > 0) old_list.pop_front(); + + // Add new_list to front in reverse + for (auto it = new_list.rbegin(); it != new_list.rend(); it++) { + old_list.push_front(*it); + } + } else { + // Remove points up to min_wcp from back + while (old_list.size() > 0 && ray_length(Ray{old_list.back().point, min_wcp.point}) > 0.01*units::cm) { + old_list.pop_back(); + } + if (old_list.size() > 0) old_list.pop_back(); + + // Add new_list to back in reverse + for (auto it = new_list.rbegin(); it != new_list.rend(); it++) { + old_list.push_back(*it); + } + } + + // Update segment wcpts + std::vector new_wcpts; + new_wcpts.reserve(old_list.size()); + std::copy(std::begin(old_list), std::end(old_list), std::back_inserter(new_wcpts)); + sg1->wcpts(new_wcpts); + + // Reconnect segment + VertexPtr tt_vtx = find_other_vertex(graph, sg1, vtx1); + if (tt_vtx && tt_vtx != main_vertex) { + remove_segment(graph, sg1); + add_segment(graph, sg1, main_vertex, tt_vtx); + } else { + // Self-loop case + remove_segment(graph, sg1); + } + } + + // Delete vtx1 and sg + remove_vertex(graph, vtx1); + remove_segment(graph, sg); + + break; + } + } + } + + // If updated, redo tracking and continue loop + if (flag_update) { + flag_continue = true; + flag_updated = true; + track_fitter.do_multi_tracking(true, true, true); + } + } + + return flag_updated; +} +bool PatternAlgorithms::examine_structure_final_3(Graph& graph, VertexPtr main_vertex, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv) { + bool flag_updated = false; + + if (!main_vertex || !main_vertex->descriptor_valid()) return flag_updated; + + // Get transform and grouping for point validation + const auto transform = track_fitter.get_pc_transforms()->pc_transform( + cluster.get_scope_transform(cluster.get_default_scope())); + double cluster_t0 = cluster.get_cluster_t0(); + auto grouping = cluster.grouping(); + + if (!transform || !grouping) return flag_updated; + + // Continue looping until no more updates + bool flag_continue = true; + while (flag_continue) { + flag_continue = false; + bool flag_update = false; + + auto main_vd = main_vertex->get_descriptor(); + + // Loop over all segments connected to main_vertex + auto [ebegin, eend] = boost::out_edges(main_vd, graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr sg = graph[*eit].segment; + if (!sg) continue; + + // Find the other vertex of this segment + VertexPtr vtx1 = find_other_vertex(graph, sg, main_vertex); + if (!vtx1 || !vtx1->descriptor_valid()) continue; + + // Skip if vtx1 has only 1 connection + auto vtx1_vd = vtx1->get_descriptor(); + if (boost::degree(vtx1_vd, graph) == 1) continue; + + // Check distance between vertices + WireCell::Point main_vtx_point = main_vertex->fit().valid() ? main_vertex->fit().point : main_vertex->wcpt().point; + WireCell::Point vtx1_point = vtx1->fit().valid() ? vtx1->fit().point : vtx1->wcpt().point; + + double dis = ray_length(Ray{main_vtx_point, vtx1_point}); + + if (dis < 2.5*units::cm) { + // Check if main_vertex can be merged into vtx1 + flag_update = true; + + // Check all segments connected to main_vertex (except sg) + auto [main_ebegin, main_eend] = boost::out_edges(main_vd, graph); + for (auto main_eit = main_ebegin; main_eit != main_eend; ++main_eit) { + SegmentPtr sg1 = graph[*main_eit].segment; + if (!sg1 || sg1 == sg) continue; + + const auto& pts = sg1->wcpts(); + if (pts.empty()) continue; + + // Determine which end of sg connects to main_vertex + const auto& sg_wcpts = sg->wcpts(); + if (sg_wcpts.empty()) continue; + + bool flag_start = (ray_length(Ray{sg_wcpts.front().point, main_vtx_point}) < 0.01*units::cm); + + // Find point at ~3cm from main_vertex + WireCell::Point min_point = pts.front().point; + double min_dis = 1e9; + int min_index = 0; + + for (size_t i = 0; i < pts.size(); i++) { + double dis = std::fabs(ray_length(Ray{pts.at(i).point, main_vtx_point}) - 3*units::cm); + if (dis < min_dis) { + min_dis = dis; + min_point = pts.at(i).point; + min_index = i; + } + } + + // Check connectivity from min_point to main_vertex + bool flag_connect = true; + if (flag_start) { + for (int i = min_index; i >= 0; i--) { + auto test_wpid = dv->contained_by(pts.at(i).point); + if (test_wpid.face() != -1 && test_wpid.apa() != -1) { + auto temp_p_raw = transform->backward(pts.at(i).point, cluster_t0, test_wpid.face(), test_wpid.apa()); + if (!grouping->is_good_point(temp_p_raw, test_wpid.apa(), test_wpid.face(), 0.2*units::cm, 0, 0)) { + flag_connect = false; + break; + } + } + } + } else { + for (size_t i = min_index; i < pts.size(); i++) { + auto test_wpid = dv->contained_by(pts.at(i).point); + if (test_wpid.face() != -1 && test_wpid.apa() != -1) { + auto temp_p_raw = transform->backward(pts.at(i).point, cluster_t0, test_wpid.face(), test_wpid.apa()); + if (!grouping->is_good_point(temp_p_raw, test_wpid.apa(), test_wpid.face(), 0.2*units::cm, 0, 0)) { + flag_connect = false; + break; + } + } + } + } + + // Check path from min_point to vtx1 + if (flag_connect) { + double step_size = 0.3 * units::cm; + WireCell::Point start_p = min_point; + WireCell::Point end_p = vtx1_point; + int ncount = std::round(ray_length(Ray{start_p, end_p}) / step_size); + int n_bad = 0; + + for (int i = 1; i < ncount; i++) { + WireCell::Point test_p( + start_p.x() + (end_p.x() - start_p.x()) / ncount * i, + start_p.y() + (end_p.y() - start_p.y()) / ncount * i, + start_p.z() + (end_p.z() - start_p.z()) / ncount * i + ); + + auto test_wpid = dv->contained_by(test_p); + if (test_wpid.face() != -1 && test_wpid.apa() != -1) { + auto temp_p_raw = transform->backward(test_p, cluster_t0, test_wpid.face(), test_wpid.apa()); + if (!grouping->is_good_point(temp_p_raw, test_wpid.apa(), test_wpid.face(), 0.3*units::cm, 0, 0)) { + n_bad++; + } + } + } + if (n_bad > 0) flag_update = false; + } + } + + // Perform the merge + if (flag_update) { + std::cout << "Cluster: " << cluster.ident() << " Final stage merge main_vertex " + // << main_vertex->ident() << " " << main_vtx_point << " " << vtx1->ident() + << " " << vtx1_point << std::endl; + + // Collect segments to update + std::vector segments_to_update; + auto [main_ebegin2, main_eend2] = boost::out_edges(main_vd, graph); + for (auto main_eit = main_ebegin2; main_eit != main_eend2; ++main_eit) { + SegmentPtr sg1 = graph[*main_eit].segment; + if (sg1 && sg1 != sg) { + segments_to_update.push_back(sg1); + } + } + + // Process each segment connected to main_vertex (except sg) + for (auto sg1 : segments_to_update) { + WCPoint vtx_new_wcp = vtx1->wcpt(); + std::vector vec_wcps = sg1->wcpts(); + + if (vec_wcps.empty()) continue; + + // Determine orientation + bool flag_front = (ray_length(Ray{vec_wcps.front().point, main_vtx_point}) < 0.01*units::cm); + + // Find point at ~3cm from main_vertex + WCPoint min_wcp = vec_wcps.front(); + double min_dis = 1e9; + + for (size_t j = 0; j < vec_wcps.size(); j++) { + double dis1 = ray_length(Ray{vec_wcps.at(j).point, main_vtx_point}); + double dis = std::fabs(dis1 - 3.0*units::cm); + if (dis < min_dis) { + min_wcp = vec_wcps.at(j); + min_dis = dis; + } + } + + // Build shortest path from vtx1 to min_wcp + std::list new_list; + new_list.push_back(vtx_new_wcp); + + // Add intermediate points using steiner point cloud + { + double dis_step = 1.0*units::cm; + int ncount = std::round(ray_length(Ray{vtx_new_wcp.point, min_wcp.point}) / dis_step); + if (ncount < 2) ncount = 2; + + for (int qx = 1; qx < ncount; qx++) { + WireCell::Point tmp_p( + vtx_new_wcp.point.x() + (min_wcp.point.x() - vtx_new_wcp.point.x()) / ncount * qx, + vtx_new_wcp.point.y() + (min_wcp.point.y() - vtx_new_wcp.point.y()) / ncount * qx, + vtx_new_wcp.point.z() + (min_wcp.point.z() - vtx_new_wcp.point.z()) / ncount * qx + ); + + auto [tmp_idx, tmp_wcp_pt] = cluster.get_closest_wcpoint(tmp_p); + WCPoint tmp_wcp; + tmp_wcp.point = tmp_wcp_pt; + + // Check distance + if (ray_length(Ray{tmp_wcp.point, tmp_p}) > 0.3*units::cm) continue; + + // Check if different from last point and min_wcp + if (ray_length(Ray{tmp_wcp.point, new_list.back().point}) > 0.01*units::cm && + ray_length(Ray{tmp_wcp.point, min_wcp.point}) > 0.01*units::cm) { + new_list.push_back(tmp_wcp); + } + } + } + new_list.push_back(min_wcp); + + // Merge with existing wcpts + std::list old_list; + std::copy(vec_wcps.begin(), vec_wcps.end(), std::back_inserter(old_list)); + + if (flag_front) { + // Remove points up to min_wcp from front + while (old_list.size() > 0 && ray_length(Ray{old_list.front().point, min_wcp.point}) > 0.01*units::cm) { + old_list.pop_front(); + } + if (old_list.size() > 0) old_list.pop_front(); + + // Add new_list to front in reverse + for (auto it = new_list.rbegin(); it != new_list.rend(); it++) { + old_list.push_front(*it); + } + } else { + // Remove points up to min_wcp from back + while (old_list.size() > 0 && ray_length(Ray{old_list.back().point, min_wcp.point}) > 0.01*units::cm) { + old_list.pop_back(); + } + if (old_list.size() > 0) old_list.pop_back(); + + // Add new_list to back in reverse + for (auto it = new_list.rbegin(); it != new_list.rend(); it++) { + old_list.push_back(*it); + } + } + + // Update segment wcpts + std::vector new_wcpts; + new_wcpts.reserve(old_list.size()); + std::copy(std::begin(old_list), std::end(old_list), std::back_inserter(new_wcpts)); + sg1->wcpts(new_wcpts); + } + + // Update main_vertex to vtx1's position + main_vertex->wcpt(vtx1->wcpt()); + if (vtx1->fit().valid()) { + main_vertex->fit(vtx1->fit()); + } + + // Reconnect segments from vtx1 to main_vertex (except sg) + std::vector vtx1_segments; + auto [vtx1_ebegin2, vtx1_eend2] = boost::out_edges(vtx1_vd, graph); + for (auto vtx1_eit = vtx1_ebegin2; vtx1_eit != vtx1_eend2; ++vtx1_eit) { + SegmentPtr sg1 = graph[*vtx1_eit].segment; + if (sg1 && sg1 != sg) { + vtx1_segments.push_back(sg1); + } + } + + for (auto sg1 : vtx1_segments) { + VertexPtr tt_vtx = find_other_vertex(graph, sg1, vtx1); + if (tt_vtx && tt_vtx != main_vertex) { + remove_segment(graph, sg1); + add_segment(graph, sg1, main_vertex, tt_vtx); + } else { + // Self-loop case + remove_segment(graph, sg1); + } + } + + // Delete vtx1 and sg + remove_vertex(graph, vtx1); + remove_segment(graph, sg); + + break; + } + } + } + + // If updated, redo tracking and continue loop + if (flag_update) { + flag_continue = true; + flag_updated = true; + track_fitter.do_multi_tracking(true, true, true); + } + } + + return flag_updated; +} + +bool PatternAlgorithms::examine_structure_final(Graph& graph, VertexPtr main_vertex, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv) { + examine_structure_final_1(graph, main_vertex, cluster, track_fitter, dv); + examine_structure_final_1p(graph, main_vertex, cluster, track_fitter, dv); + examine_structure_final_2(graph, main_vertex, cluster, track_fitter, dv); + examine_structure_final_3(graph, main_vertex, cluster, track_fitter, dv); + return true; +} From e8ea63adefc3c8ac78d581484a185bd793c5a60d Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Tue, 16 Dec 2025 12:05:32 -0800 Subject: [PATCH 060/111] improve implementations --- clus/src/NeutrinoStructureExaminer.cxx | 165 ++----------------------- 1 file changed, 8 insertions(+), 157 deletions(-) diff --git a/clus/src/NeutrinoStructureExaminer.cxx b/clus/src/NeutrinoStructureExaminer.cxx index edae3c69..eeed84de 100644 --- a/clus/src/NeutrinoStructureExaminer.cxx +++ b/clus/src/NeutrinoStructureExaminer.cxx @@ -2565,48 +2565,11 @@ bool PatternAlgorithms::examine_structure_final_1(Graph& graph, VertexPtr main_v // If the straight line is better, replace the two segments with one new segment if (flag_replace) { - // Build list of points for the new segment using steiner point cloud - std::list wcps_list; - - // Add start point - wcps_list.push_back(vtx1->wcpt().point); - - // Add intermediate points by finding closest points in steiner point cloud - for (size_t i = 0; i < new_pts.size(); i++) { - auto [idx, closest_pt] = cluster.get_closest_wcpoint(new_pts[i]); - - // Only add if different from the last point - if (wcps_list.empty() || ray_length(Ray{wcps_list.back(), closest_pt}) > 0.01*units::cm) { - wcps_list.push_back(closest_pt); - } - } - - // Add end point if different from last point - if (wcps_list.empty() || ray_length(Ray{wcps_list.back(), vtx2->wcpt().point}) > 0.01*units::cm) { - wcps_list.push_back(vtx2->wcpt().point); - } - - // Convert list to vector - std::vector path_points(wcps_list.begin(), wcps_list.end()); - - if (path_points.size() > 1) { - // Create new segment - SegmentPtr sg3 = create_segment_for_cluster(cluster, dv, path_points); - if (sg3) { - // Add new segment to graph - add_segment(graph, sg3, vtx1, vtx2); - - // Delete old segments - remove_segment(graph, sg1); - remove_segment(graph, sg2); - - // Delete the middle vertex - remove_vertex(graph, vtx); - - flag_update = true; - flag_continue = true; - break; - } + // Use helper function to merge the two segments + if (merge_two_segments_into_one(graph, sg1, vtx, sg2, dv)) { + flag_update = true; + flag_continue = true; + break; } } } @@ -3015,123 +2978,11 @@ bool PatternAlgorithms::examine_structure_final_2(Graph& graph, VertexPtr main_v // Perform the merge if (flag_update) { std::cout << "Cluster: " << cluster.ident() << " Final stage merge vertex to main vertex " - // << vtx1->ident() << " " << vtx1_point << " " << main_vertex->ident() + // << vtx1->ident() << " " << vtx1_point << " into " << main_vertex->ident() << " " << main_vtx_point << std::endl; - // Collect segments to reconnect - std::vector segments_to_reconnect; - auto [vtx1_ebegin2, vtx1_eend2] = boost::out_edges(vtx1_vd, graph); - for (auto vtx1_eit = vtx1_ebegin2; vtx1_eit != vtx1_eend2; ++vtx1_eit) { - SegmentPtr sg1 = graph[*vtx1_eit].segment; - if (sg1 && sg1 != sg) { - segments_to_reconnect.push_back(sg1); - } - } - - // Process each segment to reconnect - for (auto sg1 : segments_to_reconnect) { - WCPoint vtx_new_wcp = main_vertex->wcpt(); - std::vector vec_wcps = sg1->wcpts(); - - if (vec_wcps.empty()) continue; - - // Determine orientation - bool flag_front = (ray_length(Ray{vec_wcps.front().point, vtx1_point}) < 0.01*units::cm); - - // Find point at ~3cm from vtx1 - WCPoint min_wcp = vec_wcps.front(); - double min_dis = 1e9; - - for (size_t j = 0; j < vec_wcps.size(); j++) { - double dis1 = ray_length(Ray{vec_wcps.at(j).point, vtx1_point}); - double dis = std::fabs(dis1 - 3.0*units::cm); - if (dis < min_dis) { - min_wcp = vec_wcps.at(j); - min_dis = dis; - } - } - - // Build shortest path from main_vertex to min_wcp - std::list new_list; - new_list.push_back(vtx_new_wcp); - - // Add intermediate points using steiner point cloud - { - double dis_step = 1.0*units::cm; - int ncount = std::round(ray_length(Ray{vtx_new_wcp.point, min_wcp.point}) / dis_step); - if (ncount < 2) ncount = 2; - - for (int qx = 1; qx < ncount; qx++) { - WireCell::Point tmp_p( - vtx_new_wcp.point.x() + (min_wcp.point.x() - vtx_new_wcp.point.x()) / ncount * qx, - vtx_new_wcp.point.y() + (min_wcp.point.y() - vtx_new_wcp.point.y()) / ncount * qx, - vtx_new_wcp.point.z() + (min_wcp.point.z() - vtx_new_wcp.point.z()) / ncount * qx - ); - - auto [tmp_idx, tmp_wcp_pt] = cluster.get_closest_wcpoint(tmp_p); - WCPoint tmp_wcp; - tmp_wcp.point = tmp_wcp_pt; - - // Check distance - if (ray_length(Ray{tmp_wcp.point, tmp_p}) > 0.3*units::cm) continue; - - // Check if different from last point and min_wcp - if (ray_length(Ray{tmp_wcp.point, new_list.back().point}) > 0.01*units::cm && - ray_length(Ray{tmp_wcp.point, min_wcp.point}) > 0.01*units::cm) { - new_list.push_back(tmp_wcp); - } - } - } - new_list.push_back(min_wcp); - - // Merge with existing wcpts - std::list old_list; - std::copy(vec_wcps.begin(), vec_wcps.end(), std::back_inserter(old_list)); - - if (flag_front) { - // Remove points up to min_wcp from front - while (old_list.size() > 0 && ray_length(Ray{old_list.front().point, min_wcp.point}) > 0.01*units::cm) { - old_list.pop_front(); - } - if (old_list.size() > 0) old_list.pop_front(); - - // Add new_list to front in reverse - for (auto it = new_list.rbegin(); it != new_list.rend(); it++) { - old_list.push_front(*it); - } - } else { - // Remove points up to min_wcp from back - while (old_list.size() > 0 && ray_length(Ray{old_list.back().point, min_wcp.point}) > 0.01*units::cm) { - old_list.pop_back(); - } - if (old_list.size() > 0) old_list.pop_back(); - - // Add new_list to back in reverse - for (auto it = new_list.rbegin(); it != new_list.rend(); it++) { - old_list.push_back(*it); - } - } - - // Update segment wcpts - std::vector new_wcpts; - new_wcpts.reserve(old_list.size()); - std::copy(std::begin(old_list), std::end(old_list), std::back_inserter(new_wcpts)); - sg1->wcpts(new_wcpts); - - // Reconnect segment - VertexPtr tt_vtx = find_other_vertex(graph, sg1, vtx1); - if (tt_vtx && tt_vtx != main_vertex) { - remove_segment(graph, sg1); - add_segment(graph, sg1, main_vertex, tt_vtx); - } else { - // Self-loop case - remove_segment(graph, sg1); - } - } - - // Delete vtx1 and sg - remove_vertex(graph, vtx1); - remove_segment(graph, sg); + // Use helper function to merge vtx1 into main_vertex + merge_vertex_into_another(graph, vtx1, main_vertex, dv); break; } From d5b4adabb7216df1c31e78c3663b4b3a5d9298fc Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Wed, 17 Dec 2025 07:09:00 -0800 Subject: [PATCH 061/111] implement the search_for_vertex_activities --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 3 + clus/inc/WireCellClus/NeutrinoVertexFinder.h | 206 +++++++++++++++++++ 2 files changed, 209 insertions(+) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index f5fb3f6b..71c94bbf 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -78,5 +78,8 @@ namespace WireCell::Clus::PR { bool examine_structure_final_3(Graph& graph, VertexPtr main_vertex, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); bool examine_structure_final(Graph& graph, VertexPtr main_vertex, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); + // vertex related functions + bool search_for_vertex_activities(Graph& graph, VertexPtr vertex, std::set& segments_set, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, double search_range = 1.5*units::cm); + }; } diff --git a/clus/inc/WireCellClus/NeutrinoVertexFinder.h b/clus/inc/WireCellClus/NeutrinoVertexFinder.h index e69de29b..fd09470a 100644 --- a/clus/inc/WireCellClus/NeutrinoVertexFinder.h +++ b/clus/inc/WireCellClus/NeutrinoVertexFinder.h @@ -0,0 +1,206 @@ +#include "WireCellClus/NeutrinoPatternBase.h" + +bool WireCell::Clus::PR::PatternAlgorithms::search_for_vertex_activities(Graph& graph, VertexPtr vertex, std::set& segments_set, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, double search_range){ + // Get steiner point cloud and terminal flags + const auto& steiner_pc = cluster.get_pc("steiner_pc"); + const auto& coords = cluster.get_default_scope().coords; + const auto& x_coords = steiner_pc.get(coords.at(0))->elements(); + const auto& y_coords = steiner_pc.get(coords.at(1))->elements(); + const auto& z_coords = steiner_pc.get(coords.at(2))->elements(); + const auto& flag_steiner_terminal = steiner_pc.get("flag_steiner_terminal")->elements(); + + // Get transform and grouping for point validation + const auto transform = track_fitter.get_pc_transforms()->pc_transform( + cluster.get_scope_transform(cluster.get_default_scope())); + double cluster_t0 = cluster.get_cluster_t0(); + auto grouping = cluster.grouping(); + + if (!transform || !grouping) return false; + + // Get vertex position + WireCell::Point vtx_point = vertex->fit().valid() ? vertex->fit().point : vertex->wcpt().point; + + // Collect directions from existing segments + std::vector saved_dirs; + for (auto seg : segments_set) { + WireCell::Vector dir = vertex_segment_get_dir(vertex, seg, graph, 5*units::cm); + if (dir.magnitude() != 0) { + saved_dirs.push_back(dir); + } + } + + // Get candidate points within search range + auto candidate_results = cluster.kd_steiner_radius(search_range, vtx_point, "steiner_pc"); + + double max_dis = 0; + size_t max_idx = 0; + bool found = false; + + // First round: look for points with good angular separation and charge + for (const auto& [idx, dist_sq] : candidate_results) { + if (!flag_steiner_terminal[idx]) continue; + + // Skip if this is the vertex itself + if (ray_length(Ray{WireCell::Point(x_coords[idx], y_coords[idx], z_coords[idx]), vertex->wcpt().point}) < 0.01*units::cm) continue; + + double dis = std::sqrt(dist_sq); + WireCell::Point test_p(x_coords[idx], y_coords[idx], z_coords[idx]); + + // Find minimum distance to all segments + double min_dis = 1e9; + double min_dis_u = 1e9; + double min_dis_v = 1e9; + double min_dis_w = 1e9; + + auto test_wpid = dv->contained_by(test_p); + if (test_wpid.face() == -1 || test_wpid.apa() == -1) continue; + + for (auto it = boost::edges(graph).first; it != boost::edges(graph).second; ++it) { + SegmentPtr sg = graph[*it].segment; + if (!sg || sg->cluster() != &cluster) continue; + + auto [dist_3d, closest_pt] = segment_get_closest_point(sg, test_p, "fit"); + if (dist_3d < min_dis) min_dis = dist_3d; + + auto [dist_u, dist_v, dist_w] = segment_get_closest_2d_distances(sg, test_p, test_wpid.apa(), test_wpid.face(), "fit"); + if (dist_u < min_dis_u) min_dis_u = dist_u; + if (dist_v < min_dis_v) min_dis_v = dist_v; + if (dist_w < min_dis_w) min_dis_w = dist_w; + } + + if (min_dis > 0.6*units::cm && min_dis_u + min_dis_v + min_dis_w > 1.2*units::cm) { + WireCell::Vector dir(test_p.x() - vtx_point.x(), test_p.y() - vtx_point.y(), test_p.z() - vtx_point.z()); + double sum_angle = 0; + double min_angle = 1e9; + + for (size_t j = 0; j < saved_dirs.size(); j++) { + double angle = std::acos(dir.dot(saved_dirs[j]) / (dir.magnitude() * saved_dirs[j].magnitude())) / 3.1415926 * 180.0; + sum_angle += angle; + if (angle < min_angle) min_angle = angle; + } + + // Get average charge + double sum_charge = 0; + int ncount = 0; + auto test_p_raw = transform->backward(test_p, cluster_t0, test_wpid.face(), test_wpid.apa()); + + for (int plane = 0; plane < 3; plane++) { + if (!grouping->get_closest_dead_chs(test_p_raw, plane, test_wpid.apa(), test_wpid.face())) { + sum_charge += grouping->get_ave_charge(test_p_raw, 0.3*units::cm, plane, test_wpid.apa(), test_wpid.face()); + ncount++; + } + } + if (ncount != 0) sum_charge /= ncount; + + if ((sum_angle) * (sum_charge + 1e-9) > max_dis) { + max_dis = (sum_angle) * (sum_charge + 1e-9); + max_idx = idx; + found = true; + } + } + } + + // Second round: if nothing found, use relaxed criteria + if (max_dis == 0) { + for (const auto& [idx, dist_sq] : candidate_results) { + if (!flag_steiner_terminal[idx]) continue; + + // Skip if this is the vertex itself + if (ray_length(Ray{WireCell::Point(x_coords[idx], y_coords[idx], z_coords[idx]), vertex->wcpt().point}) < 0.01*units::cm) continue; + + double dis = std::sqrt(dist_sq); + WireCell::Point test_p(x_coords[idx], y_coords[idx], z_coords[idx]); + + // Find minimum distance to all segments + double min_dis = 1e9; + double min_dis_u = 1e9; + double min_dis_v = 1e9; + double min_dis_w = 1e9; + + auto test_wpid = dv->contained_by(test_p); + if (test_wpid.face() == -1 || test_wpid.apa() == -1) continue; + + for (auto it = boost::edges(graph).first; it != boost::edges(graph).second; ++it) { + SegmentPtr sg = graph[*it].segment; + if (!sg || sg->cluster() != &cluster) continue; + + auto [dist_3d, closest_pt] = segment_get_closest_point(sg, test_p, "fit"); + if (dist_3d < min_dis) min_dis = dist_3d; + + auto [dist_u, dist_v, dist_w] = segment_get_closest_2d_distances(sg, test_p, test_wpid.apa(), test_wpid.face(), "fit"); + if (dist_u < min_dis_u) min_dis_u = dist_u; + if (dist_v < min_dis_v) min_dis_v = dist_v; + if (dist_w < min_dis_w) min_dis_w = dist_w; + } + + if (min_dis > 0.36*units::cm && min_dis_u + min_dis_v + min_dis_w > 0.8*units::cm) { + // Get average charge + double sum_charge = 0; + int ncount = 0; + auto test_p_raw = transform->backward(test_p, cluster_t0, test_wpid.face(), test_wpid.apa()); + + for (int plane = 0; plane < 3; plane++) { + if (!grouping->get_closest_dead_chs(test_p_raw, plane, test_wpid.apa(), test_wpid.face())) { + sum_charge += grouping->get_ave_charge(test_p_raw, 0.3*units::cm, plane, test_wpid.apa(), test_wpid.face()); + ncount++; + } + } + if (ncount != 0) sum_charge /= ncount; + + if (min_dis + (min_dis_u + min_dis_v + min_dis_w) / std::sqrt(3.0) > max_dis && sum_charge > 20000) { + max_dis = min_dis + (min_dis_u + min_dis_v + min_dis_w) / std::sqrt(3.0); + max_idx = idx; + found = true; + } + } + } + } + + // If a good candidate was found, create new vertex and segment + if (found && max_dis != 0) { + WireCell::Point max_point(x_coords[max_idx], y_coords[max_idx], z_coords[max_idx]); + + // Create new vertex at the found point + auto v1 = make_vertex(graph); + WCPoint new_wcp; + new_wcp.point = max_point; + v1->wcpt(new_wcp).cluster(&cluster); + + // Build path from vertex to new vertex using steiner point cloud + WireCell::Point mid_p( + (vertex->wcpt().point.x() + max_point.x()) / 2.0, + (vertex->wcpt().point.y() + max_point.y()) / 2.0, + (vertex->wcpt().point.z() + max_point.z()) / 2.0 + ); + + auto [mid_idx, mid_pt] = cluster.get_closest_wcpoint(mid_p); + + std::list wcp_list; + wcp_list.push_back(vertex->wcpt().point); + + if (ray_length(Ray{mid_pt, wcp_list.back()}) > 0.01*units::cm) { + wcp_list.push_back(mid_pt); + } + + if (ray_length(Ray{max_point, wcp_list.back()}) > 0.01*units::cm) { + wcp_list.push_back(max_point); + } + + if (wcp_list.size() > 1) { + std::cout << "Cluster: " << cluster.ident() << " Vertex Activity Found at " << mid_p << std::endl; + + // Convert to vector for segment creation + std::vector path_points(wcp_list.begin(), wcp_list.end()); + + // Create new segment + auto sg1 = create_segment_for_cluster(cluster, dv, path_points, 0); + if (sg1) { + add_segment(graph, sg1, v1, vertex); + return true; + } + } + } + + return false; +} + From d0f167358eea72be26279df412db5f3be66ee8c4 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Wed, 17 Dec 2025 07:17:47 -0800 Subject: [PATCH 062/111] catch up --- clus/inc/WireCellClus/NeutrinoVertexFinder.h | 206 ------------------ clus/src/NeutrinoVertexFinder.cxx | 210 +++++++++++++++++++ 2 files changed, 210 insertions(+), 206 deletions(-) delete mode 100644 clus/inc/WireCellClus/NeutrinoVertexFinder.h diff --git a/clus/inc/WireCellClus/NeutrinoVertexFinder.h b/clus/inc/WireCellClus/NeutrinoVertexFinder.h deleted file mode 100644 index fd09470a..00000000 --- a/clus/inc/WireCellClus/NeutrinoVertexFinder.h +++ /dev/null @@ -1,206 +0,0 @@ -#include "WireCellClus/NeutrinoPatternBase.h" - -bool WireCell::Clus::PR::PatternAlgorithms::search_for_vertex_activities(Graph& graph, VertexPtr vertex, std::set& segments_set, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, double search_range){ - // Get steiner point cloud and terminal flags - const auto& steiner_pc = cluster.get_pc("steiner_pc"); - const auto& coords = cluster.get_default_scope().coords; - const auto& x_coords = steiner_pc.get(coords.at(0))->elements(); - const auto& y_coords = steiner_pc.get(coords.at(1))->elements(); - const auto& z_coords = steiner_pc.get(coords.at(2))->elements(); - const auto& flag_steiner_terminal = steiner_pc.get("flag_steiner_terminal")->elements(); - - // Get transform and grouping for point validation - const auto transform = track_fitter.get_pc_transforms()->pc_transform( - cluster.get_scope_transform(cluster.get_default_scope())); - double cluster_t0 = cluster.get_cluster_t0(); - auto grouping = cluster.grouping(); - - if (!transform || !grouping) return false; - - // Get vertex position - WireCell::Point vtx_point = vertex->fit().valid() ? vertex->fit().point : vertex->wcpt().point; - - // Collect directions from existing segments - std::vector saved_dirs; - for (auto seg : segments_set) { - WireCell::Vector dir = vertex_segment_get_dir(vertex, seg, graph, 5*units::cm); - if (dir.magnitude() != 0) { - saved_dirs.push_back(dir); - } - } - - // Get candidate points within search range - auto candidate_results = cluster.kd_steiner_radius(search_range, vtx_point, "steiner_pc"); - - double max_dis = 0; - size_t max_idx = 0; - bool found = false; - - // First round: look for points with good angular separation and charge - for (const auto& [idx, dist_sq] : candidate_results) { - if (!flag_steiner_terminal[idx]) continue; - - // Skip if this is the vertex itself - if (ray_length(Ray{WireCell::Point(x_coords[idx], y_coords[idx], z_coords[idx]), vertex->wcpt().point}) < 0.01*units::cm) continue; - - double dis = std::sqrt(dist_sq); - WireCell::Point test_p(x_coords[idx], y_coords[idx], z_coords[idx]); - - // Find minimum distance to all segments - double min_dis = 1e9; - double min_dis_u = 1e9; - double min_dis_v = 1e9; - double min_dis_w = 1e9; - - auto test_wpid = dv->contained_by(test_p); - if (test_wpid.face() == -1 || test_wpid.apa() == -1) continue; - - for (auto it = boost::edges(graph).first; it != boost::edges(graph).second; ++it) { - SegmentPtr sg = graph[*it].segment; - if (!sg || sg->cluster() != &cluster) continue; - - auto [dist_3d, closest_pt] = segment_get_closest_point(sg, test_p, "fit"); - if (dist_3d < min_dis) min_dis = dist_3d; - - auto [dist_u, dist_v, dist_w] = segment_get_closest_2d_distances(sg, test_p, test_wpid.apa(), test_wpid.face(), "fit"); - if (dist_u < min_dis_u) min_dis_u = dist_u; - if (dist_v < min_dis_v) min_dis_v = dist_v; - if (dist_w < min_dis_w) min_dis_w = dist_w; - } - - if (min_dis > 0.6*units::cm && min_dis_u + min_dis_v + min_dis_w > 1.2*units::cm) { - WireCell::Vector dir(test_p.x() - vtx_point.x(), test_p.y() - vtx_point.y(), test_p.z() - vtx_point.z()); - double sum_angle = 0; - double min_angle = 1e9; - - for (size_t j = 0; j < saved_dirs.size(); j++) { - double angle = std::acos(dir.dot(saved_dirs[j]) / (dir.magnitude() * saved_dirs[j].magnitude())) / 3.1415926 * 180.0; - sum_angle += angle; - if (angle < min_angle) min_angle = angle; - } - - // Get average charge - double sum_charge = 0; - int ncount = 0; - auto test_p_raw = transform->backward(test_p, cluster_t0, test_wpid.face(), test_wpid.apa()); - - for (int plane = 0; plane < 3; plane++) { - if (!grouping->get_closest_dead_chs(test_p_raw, plane, test_wpid.apa(), test_wpid.face())) { - sum_charge += grouping->get_ave_charge(test_p_raw, 0.3*units::cm, plane, test_wpid.apa(), test_wpid.face()); - ncount++; - } - } - if (ncount != 0) sum_charge /= ncount; - - if ((sum_angle) * (sum_charge + 1e-9) > max_dis) { - max_dis = (sum_angle) * (sum_charge + 1e-9); - max_idx = idx; - found = true; - } - } - } - - // Second round: if nothing found, use relaxed criteria - if (max_dis == 0) { - for (const auto& [idx, dist_sq] : candidate_results) { - if (!flag_steiner_terminal[idx]) continue; - - // Skip if this is the vertex itself - if (ray_length(Ray{WireCell::Point(x_coords[idx], y_coords[idx], z_coords[idx]), vertex->wcpt().point}) < 0.01*units::cm) continue; - - double dis = std::sqrt(dist_sq); - WireCell::Point test_p(x_coords[idx], y_coords[idx], z_coords[idx]); - - // Find minimum distance to all segments - double min_dis = 1e9; - double min_dis_u = 1e9; - double min_dis_v = 1e9; - double min_dis_w = 1e9; - - auto test_wpid = dv->contained_by(test_p); - if (test_wpid.face() == -1 || test_wpid.apa() == -1) continue; - - for (auto it = boost::edges(graph).first; it != boost::edges(graph).second; ++it) { - SegmentPtr sg = graph[*it].segment; - if (!sg || sg->cluster() != &cluster) continue; - - auto [dist_3d, closest_pt] = segment_get_closest_point(sg, test_p, "fit"); - if (dist_3d < min_dis) min_dis = dist_3d; - - auto [dist_u, dist_v, dist_w] = segment_get_closest_2d_distances(sg, test_p, test_wpid.apa(), test_wpid.face(), "fit"); - if (dist_u < min_dis_u) min_dis_u = dist_u; - if (dist_v < min_dis_v) min_dis_v = dist_v; - if (dist_w < min_dis_w) min_dis_w = dist_w; - } - - if (min_dis > 0.36*units::cm && min_dis_u + min_dis_v + min_dis_w > 0.8*units::cm) { - // Get average charge - double sum_charge = 0; - int ncount = 0; - auto test_p_raw = transform->backward(test_p, cluster_t0, test_wpid.face(), test_wpid.apa()); - - for (int plane = 0; plane < 3; plane++) { - if (!grouping->get_closest_dead_chs(test_p_raw, plane, test_wpid.apa(), test_wpid.face())) { - sum_charge += grouping->get_ave_charge(test_p_raw, 0.3*units::cm, plane, test_wpid.apa(), test_wpid.face()); - ncount++; - } - } - if (ncount != 0) sum_charge /= ncount; - - if (min_dis + (min_dis_u + min_dis_v + min_dis_w) / std::sqrt(3.0) > max_dis && sum_charge > 20000) { - max_dis = min_dis + (min_dis_u + min_dis_v + min_dis_w) / std::sqrt(3.0); - max_idx = idx; - found = true; - } - } - } - } - - // If a good candidate was found, create new vertex and segment - if (found && max_dis != 0) { - WireCell::Point max_point(x_coords[max_idx], y_coords[max_idx], z_coords[max_idx]); - - // Create new vertex at the found point - auto v1 = make_vertex(graph); - WCPoint new_wcp; - new_wcp.point = max_point; - v1->wcpt(new_wcp).cluster(&cluster); - - // Build path from vertex to new vertex using steiner point cloud - WireCell::Point mid_p( - (vertex->wcpt().point.x() + max_point.x()) / 2.0, - (vertex->wcpt().point.y() + max_point.y()) / 2.0, - (vertex->wcpt().point.z() + max_point.z()) / 2.0 - ); - - auto [mid_idx, mid_pt] = cluster.get_closest_wcpoint(mid_p); - - std::list wcp_list; - wcp_list.push_back(vertex->wcpt().point); - - if (ray_length(Ray{mid_pt, wcp_list.back()}) > 0.01*units::cm) { - wcp_list.push_back(mid_pt); - } - - if (ray_length(Ray{max_point, wcp_list.back()}) > 0.01*units::cm) { - wcp_list.push_back(max_point); - } - - if (wcp_list.size() > 1) { - std::cout << "Cluster: " << cluster.ident() << " Vertex Activity Found at " << mid_p << std::endl; - - // Convert to vector for segment creation - std::vector path_points(wcp_list.begin(), wcp_list.end()); - - // Create new segment - auto sg1 = create_segment_for_cluster(cluster, dv, path_points, 0); - if (sg1) { - add_segment(graph, sg1, v1, vertex); - return true; - } - } - } - - return false; -} - diff --git a/clus/src/NeutrinoVertexFinder.cxx b/clus/src/NeutrinoVertexFinder.cxx index e69de29b..07b064b8 100644 --- a/clus/src/NeutrinoVertexFinder.cxx +++ b/clus/src/NeutrinoVertexFinder.cxx @@ -0,0 +1,210 @@ +#include "WireCellClus/NeutrinoPatternBase.h" +#include "WireCellClus/PRSegmentFunctions.h" + +using namespace WireCell::Clus::PR; +using namespace WireCell::Clus; + +bool WireCell::Clus::PR::PatternAlgorithms::search_for_vertex_activities(Graph& graph, VertexPtr vertex, std::set& segments_set, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, double search_range){ + // Get steiner point cloud and terminal flags + const auto& steiner_pc = cluster.get_pc("steiner_pc"); + const auto& coords = cluster.get_default_scope().coords; + const auto& x_coords = steiner_pc.get(coords.at(0))->elements(); + const auto& y_coords = steiner_pc.get(coords.at(1))->elements(); + const auto& z_coords = steiner_pc.get(coords.at(2))->elements(); + const auto& flag_steiner_terminal = steiner_pc.get("flag_steiner_terminal")->elements(); + + // Get transform and grouping for point validation + const auto transform = track_fitter.get_pc_transforms()->pc_transform( + cluster.get_scope_transform(cluster.get_default_scope())); + double cluster_t0 = cluster.get_cluster_t0(); + auto grouping = cluster.grouping(); + + if (!transform || !grouping) return false; + + // Get vertex position + WireCell::Point vtx_point = vertex->fit().valid() ? vertex->fit().point : vertex->wcpt().point; + + // Collect directions from existing segments + std::vector saved_dirs; + for (auto seg : segments_set) { + WireCell::Vector dir = vertex_segment_get_dir(vertex, seg, graph, 5*units::cm); + if (dir.magnitude() != 0) { + saved_dirs.push_back(dir); + } + } + + // Get candidate points within search range + auto candidate_results = cluster.kd_steiner_radius(search_range, vtx_point, "steiner_pc"); + + double max_dis = 0; + size_t max_idx = 0; + bool found = false; + + // First round: look for points with good angular separation and charge + for (const auto& [idx, dist_sq] : candidate_results) { + if (!flag_steiner_terminal[idx]) continue; + + // Skip if this is the vertex itself + if (ray_length(Ray{WireCell::Point(x_coords[idx], y_coords[idx], z_coords[idx]), vertex->wcpt().point}) < 0.01*units::cm) continue; + + // double dis = std::sqrt(dist_sq); + WireCell::Point test_p(x_coords[idx], y_coords[idx], z_coords[idx]); + + // Find minimum distance to all segments + double min_dis = 1e9; + double min_dis_u = 1e9; + double min_dis_v = 1e9; + double min_dis_w = 1e9; + + auto test_wpid = dv->contained_by(test_p); + if (test_wpid.face() == -1 || test_wpid.apa() == -1) continue; + + for (auto it = boost::edges(graph).first; it != boost::edges(graph).second; ++it) { + SegmentPtr sg = graph[*it].segment; + if (!sg || sg->cluster() != &cluster) continue; + + auto [dist_3d, closest_pt] = segment_get_closest_point(sg, test_p, "fit"); + if (dist_3d < min_dis) min_dis = dist_3d; + + auto [dist_u, dist_v, dist_w] = segment_get_closest_2d_distances(sg, test_p, test_wpid.apa(), test_wpid.face(), "fit"); + if (dist_u < min_dis_u) min_dis_u = dist_u; + if (dist_v < min_dis_v) min_dis_v = dist_v; + if (dist_w < min_dis_w) min_dis_w = dist_w; + } + + if (min_dis > 0.6*units::cm && min_dis_u + min_dis_v + min_dis_w > 1.2*units::cm) { + WireCell::Vector dir(test_p.x() - vtx_point.x(), test_p.y() - vtx_point.y(), test_p.z() - vtx_point.z()); + double sum_angle = 0; + double min_angle = 1e9; + + for (size_t j = 0; j < saved_dirs.size(); j++) { + double angle = std::acos(dir.dot(saved_dirs[j]) / (dir.magnitude() * saved_dirs[j].magnitude())) / 3.1415926 * 180.0; + sum_angle += angle; + if (angle < min_angle) min_angle = angle; + } + + // Get average charge + double sum_charge = 0; + int ncount = 0; + auto test_p_raw = transform->backward(test_p, cluster_t0, test_wpid.face(), test_wpid.apa()); + + for (int plane = 0; plane < 3; plane++) { + if (!grouping->get_closest_dead_chs(test_p_raw, 1, test_wpid.apa(), test_wpid.face(), plane)) { + sum_charge += grouping->get_ave_charge(test_p_raw, 0.3*units::cm, plane, test_wpid.apa(), test_wpid.face()); + ncount++; + } + } + if (ncount != 0) sum_charge /= ncount; + + if ((sum_angle) * (sum_charge + 1e-9) > max_dis) { + max_dis = (sum_angle) * (sum_charge + 1e-9); + max_idx = idx; + found = true; + } + } + } + + // Second round: if nothing found, use relaxed criteria + if (max_dis == 0) { + for (const auto& [idx, dist_sq] : candidate_results) { + if (!flag_steiner_terminal[idx]) continue; + + // Skip if this is the vertex itself + if (ray_length(Ray{WireCell::Point(x_coords[idx], y_coords[idx], z_coords[idx]), vertex->wcpt().point}) < 0.01*units::cm) continue; + + // double dis = std::sqrt(dist_sq); + WireCell::Point test_p(x_coords[idx], y_coords[idx], z_coords[idx]); + + // Find minimum distance to all segments + double min_dis = 1e9; + double min_dis_u = 1e9; + double min_dis_v = 1e9; + double min_dis_w = 1e9; + + auto test_wpid = dv->contained_by(test_p); + if (test_wpid.face() == -1 || test_wpid.apa() == -1) continue; + + for (auto it = boost::edges(graph).first; it != boost::edges(graph).second; ++it) { + SegmentPtr sg = graph[*it].segment; + if (!sg || sg->cluster() != &cluster) continue; + + auto [dist_3d, closest_pt] = segment_get_closest_point(sg, test_p, "fit"); + if (dist_3d < min_dis) min_dis = dist_3d; + + auto [dist_u, dist_v, dist_w] = segment_get_closest_2d_distances(sg, test_p, test_wpid.apa(), test_wpid.face(), "fit"); + if (dist_u < min_dis_u) min_dis_u = dist_u; + if (dist_v < min_dis_v) min_dis_v = dist_v; + if (dist_w < min_dis_w) min_dis_w = dist_w; + } + + if (min_dis > 0.36*units::cm && min_dis_u + min_dis_v + min_dis_w > 0.8*units::cm) { + // Get average charge + double sum_charge = 0; + int ncount = 0; + auto test_p_raw = transform->backward(test_p, cluster_t0, test_wpid.face(), test_wpid.apa()); + + for (int plane = 0; plane < 3; plane++) { + if (!grouping->get_closest_dead_chs(test_p_raw, 1, test_wpid.apa(), test_wpid.face(), plane)) { + sum_charge += grouping->get_ave_charge(test_p_raw, 0.3*units::cm, plane, test_wpid.apa(), test_wpid.face()); + ncount++; + } + } + if (ncount != 0) sum_charge /= ncount; + + if (min_dis + (min_dis_u + min_dis_v + min_dis_w) / std::sqrt(3.0) > max_dis && sum_charge > 20000) { + max_dis = min_dis + (min_dis_u + min_dis_v + min_dis_w) / std::sqrt(3.0); + max_idx = idx; + found = true; + } + } + } + } + + // If a good candidate was found, create new vertex and segment + if (found && max_dis != 0) { + WireCell::Point max_point(x_coords[max_idx], y_coords[max_idx], z_coords[max_idx]); + + // Create new vertex at the found point + auto v1 = make_vertex(graph); + WCPoint new_wcp; + new_wcp.point = max_point; + v1->wcpt(new_wcp).cluster(&cluster); + + // Build path from vertex to new vertex using steiner point cloud + WireCell::Point mid_p( + (vertex->wcpt().point.x() + max_point.x()) / 2.0, + (vertex->wcpt().point.y() + max_point.y()) / 2.0, + (vertex->wcpt().point.z() + max_point.z()) / 2.0 + ); + + auto [mid_idx, mid_pt] = cluster.get_closest_wcpoint(mid_p); + + std::list wcp_list; + wcp_list.push_back(vertex->wcpt().point); + + if (ray_length(Ray{mid_pt, wcp_list.back()}) > 0.01*units::cm) { + wcp_list.push_back(mid_pt); + } + + if (ray_length(Ray{max_point, wcp_list.back()}) > 0.01*units::cm) { + wcp_list.push_back(max_point); + } + + if (wcp_list.size() > 1) { + std::cout << "Cluster: " << cluster.ident() << " Vertex Activity Found at " << mid_p << std::endl; + + // Convert to vector for segment creation + std::vector path_points(wcp_list.begin(), wcp_list.end()); + + // Create new segment + auto sg1 = create_segment_for_cluster(cluster, dv, path_points, 0); + if (sg1) { + add_segment(graph, sg1, v1, vertex); + return true; + } + } + } + + return false; +} + From 9498c3baa8b8f0d6964697152f74ec7a3602fd8b Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Wed, 17 Dec 2025 08:01:05 -0800 Subject: [PATCH 063/111] catch up --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 3 +++ clus/src/NeutrinoTrackShowerSep.cxx | 23 +++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 71c94bbf..5a6d46e1 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -78,6 +78,9 @@ namespace WireCell::Clus::PR { bool examine_structure_final_3(Graph& graph, VertexPtr main_vertex, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); bool examine_structure_final(Graph& graph, VertexPtr main_vertex, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); + // EM shower related + void clustering_points(Graph& graph, Facade::Cluster& cluster, const IDetectorVolumes::pointer& dv, const std::string& cloud_name = "associated_points", double search_range = 1.2*units::cm, double scaling_2d = 0.7); + // vertex related functions bool search_for_vertex_activities(Graph& graph, VertexPtr vertex, std::set& segments_set, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, double search_range = 1.5*units::cm); diff --git a/clus/src/NeutrinoTrackShowerSep.cxx b/clus/src/NeutrinoTrackShowerSep.cxx index e69de29b..d76b3b71 100644 --- a/clus/src/NeutrinoTrackShowerSep.cxx +++ b/clus/src/NeutrinoTrackShowerSep.cxx @@ -0,0 +1,23 @@ +#include "WireCellClus/NeutrinoPatternBase.h" +#include "WireCellClus/PRSegmentFunctions.h" + +using namespace WireCell::Clus::PR; +using namespace WireCell::Clus; + +void PatternAlgorithms::clustering_points(Graph& graph, Facade::Cluster& cluster, const IDetectorVolumes::pointer& dv, const std::string& cloud_name, double search_range, double scaling_2d){ + // Collect all segments that belong to this cluster + std::set segments; + + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr seg = graph[*eit].segment; + if (seg && seg->cluster() == &cluster) { + segments.insert(seg); + } + } + + // Run clustering on the collected segments + if (!segments.empty()) { + clustering_points_segments(segments, dv, cloud_name, search_range, scaling_2d); + } +} \ No newline at end of file From 32b80f34a255d936ebdef741f6ef6b12728a9656 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Wed, 17 Dec 2025 09:55:12 -0800 Subject: [PATCH 064/111] add the local to global indices mapping --- clus/inc/WireCellClus/PRSegment.h | 17 +++++++++++++++++ clus/inc/WireCellClus/PRSegmentFunctions.h | 3 ++- clus/src/PRSegmentFunctions.cxx | 15 ++++++++++++--- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/clus/inc/WireCellClus/PRSegment.h b/clus/inc/WireCellClus/PRSegment.h index c4c8d58d..3266f905 100644 --- a/clus/inc/WireCellClus/PRSegment.h +++ b/clus/inc/WireCellClus/PRSegment.h @@ -117,6 +117,20 @@ namespace WireCell::Clus::PR { void set_fit_associate_vec(std::vector& tmp_fit_vec, const IDetectorVolumes::pointer& dv,const std::string& cloud_name="fit"); + // Global indices management for point clouds + void set_global_indices(const std::string& cloud_name, std::vector indices) { + m_pc_global_indices[cloud_name] = std::move(indices); + } + + const std::vector& global_indices(const std::string& cloud_name) const { + static const std::vector empty_vec; + auto it = m_pc_global_indices.find(cloud_name); + return (it != m_pc_global_indices.end()) ? it->second : empty_vec; + } + + bool has_global_indices(const std::string& cloud_name) const { + return m_pc_global_indices.find(cloud_name) != m_pc_global_indices.end(); + } private: @@ -127,6 +141,9 @@ namespace WireCell::Clus::PR { bool m_dir_weak{false}; std::shared_ptr m_particle_info{nullptr}; + + // Mapping from local DPC index to global cluster point index + std::map> m_pc_global_indices; diff --git a/clus/inc/WireCellClus/PRSegmentFunctions.h b/clus/inc/WireCellClus/PRSegmentFunctions.h index d9d00d05..2451c09f 100644 --- a/clus/inc/WireCellClus/PRSegmentFunctions.h +++ b/clus/inc/WireCellClus/PRSegmentFunctions.h @@ -74,7 +74,8 @@ namespace WireCell::Clus::PR { void create_segment_point_cloud(SegmentPtr segment, const std::vector& path_points, const IDetectorVolumes::pointer& dv, - const std::string& cloud_name = "main"); + const std::string& cloud_name = "main", + const std::vector& global_indices = {}); void create_segment_fit_point_cloud(SegmentPtr segment, const IDetectorVolumes::pointer& dv, diff --git a/clus/src/PRSegmentFunctions.cxx b/clus/src/PRSegmentFunctions.cxx index ec5f0789..a531b360 100644 --- a/clus/src/PRSegmentFunctions.cxx +++ b/clus/src/PRSegmentFunctions.cxx @@ -12,7 +12,8 @@ namespace WireCell::Clus::PR { void create_segment_point_cloud(SegmentPtr segment, const std::vector& path_points, const IDetectorVolumes::pointer& dv, - const std::string& cloud_name) + const std::string& cloud_name, + const std::vector& global_indices) { if (!segment || !segment->cluster()) { raise("create_segment_point_cloud: invalid segment or missing cluster"); @@ -52,6 +53,11 @@ namespace WireCell::Clus::PR { // Associate with segment segment->dpcloud(cloud_name, dpc); + + // Store global indices if provided + if (!global_indices.empty()) { + segment->set_global_indices(cloud_name, global_indices); + } } void create_segment_fit_point_cloud(SegmentPtr segment, @@ -1620,6 +1626,7 @@ namespace WireCell::Clus::PR { const auto& graph = clus->find_graph("basic_pid"); std::map> map_segment_points; + std::map> map_segment_global_indices; std::map> map_pindex_segment; //std::cout << "Cluster has " << npoints << " points and " << segs.size() << " segments." << std::endl; //std::cout << "Number of vertices in the graph: " << boost::num_vertices(graph) << std::endl; @@ -1751,7 +1758,7 @@ namespace WireCell::Clus::PR { // change the point's clustering ... if (!flag_change){ - map_segment_points[main_sg].push_back(gp); + map_segment_global_indices[main_sg].push_back(i); } } } @@ -1760,7 +1767,9 @@ namespace WireCell::Clus::PR { // convert points to geo_point_t format // add points to segments ... for (const auto& [seg, geo_points] : map_segment_points) { - create_segment_point_cloud(seg, geo_points, dv, cloud_name); + const auto& global_indices = map_segment_global_indices[seg]; + create_segment_point_cloud(seg, geo_points, dv, cloud_name, global_indices); + // create_segment_point_cloud(seg, geo_points, dv, cloud_name); } } } From 5bbb93d4bc49bd09240526711f35af6640a7cb57 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Wed, 17 Dec 2025 10:15:09 -0800 Subject: [PATCH 065/111] update code --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 1 + clus/src/NeutrinoTrackShowerSep.cxx | 21 ++++++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 5a6d46e1..0320fef1 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -80,6 +80,7 @@ namespace WireCell::Clus::PR { // EM shower related void clustering_points(Graph& graph, Facade::Cluster& cluster, const IDetectorVolumes::pointer& dv, const std::string& cloud_name = "associated_points", double search_range = 1.2*units::cm, double scaling_2d = 0.7); + void separate_track_shower(Graph&graph, Facade::Cluster& cluster); // vertex related functions bool search_for_vertex_activities(Graph& graph, VertexPtr vertex, std::set& segments_set, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, double search_range = 1.5*units::cm); diff --git a/clus/src/NeutrinoTrackShowerSep.cxx b/clus/src/NeutrinoTrackShowerSep.cxx index d76b3b71..0f2068b0 100644 --- a/clus/src/NeutrinoTrackShowerSep.cxx +++ b/clus/src/NeutrinoTrackShowerSep.cxx @@ -20,4 +20,23 @@ void PatternAlgorithms::clustering_points(Graph& graph, Facade::Cluster& cluster if (!segments.empty()) { clustering_points_segments(segments, dv, cloud_name, search_range, scaling_2d); } -} \ No newline at end of file +} + +void PatternAlgorithms::separate_track_shower(Graph&graph, Facade::Cluster& cluster) { + // Iterate through all edges (segments) in the graph + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr seg = graph[*eit].segment; + + // Skip if segment is null or doesn't belong to this cluster + if (!seg || seg->cluster() != &cluster) continue; + + // First check if segment is a shower topology + segment_is_shower_topology(seg); + + // If not shower topology, check if it's a shower trajectory + if (!seg->flags_any(SegmentFlags::kShowerTopology)) { + segment_is_shower_trajectory(seg); + } + } +} From 12142eff2a1710e0de3dd5141843064c9309d6c9 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Wed, 17 Dec 2025 11:12:16 -0800 Subject: [PATCH 066/111] add a function to transfer information from segment to cluster --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 4 ++ clus/inc/WireCellClus/PRSegment.h | 6 +++ clus/src/NeutrinoPatternBase.cxx | 57 +++++++++++++++++++++ 3 files changed, 67 insertions(+) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 0320fef1..5d8322b4 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -82,8 +82,12 @@ namespace WireCell::Clus::PR { void clustering_points(Graph& graph, Facade::Cluster& cluster, const IDetectorVolumes::pointer& dv, const std::string& cloud_name = "associated_points", double search_range = 1.2*units::cm, double scaling_2d = 0.7); void separate_track_shower(Graph&graph, Facade::Cluster& cluster); + // vertex related functions bool search_for_vertex_activities(Graph& graph, VertexPtr vertex, std::set& segments_set, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, double search_range = 1.5*units::cm); + // global information transfer + void transfer_info_from_segment_to_cluster(Graph& graph, Facade::Cluster& cluster, const std::string& cloud_name = "associated_points"); + }; } diff --git a/clus/inc/WireCellClus/PRSegment.h b/clus/inc/WireCellClus/PRSegment.h index 3266f905..b9b19430 100644 --- a/clus/inc/WireCellClus/PRSegment.h +++ b/clus/inc/WireCellClus/PRSegment.h @@ -131,8 +131,14 @@ namespace WireCell::Clus::PR { bool has_global_indices(const std::string& cloud_name) const { return m_pc_global_indices.find(cloud_name) != m_pc_global_indices.end(); } + + // Add public accessor: + int id() const { return m_id; } + void set_id(int id) { m_id = id; } private: + // Add to PRSegment.h private section: + int m_id{-1}; std::vector m_wcpts; std::vector m_fits; diff --git a/clus/src/NeutrinoPatternBase.cxx b/clus/src/NeutrinoPatternBase.cxx index bc79f872..82298183 100644 --- a/clus/src/NeutrinoPatternBase.cxx +++ b/clus/src/NeutrinoPatternBase.cxx @@ -1144,3 +1144,60 @@ void PatternAlgorithms::init_point_segment(Graph& graph, Facade::Cluster& cluste track_fitter.add_segment(sg1); track_fitter.do_multi_tracking(true, true, true); } + +void PatternAlgorithms::transfer_info_from_segment_to_cluster(Graph& graph, Facade::Cluster& cluster, const std::string& cloud_name){ + // Get the number of points in the cluster + const size_t npoints = cluster.npoints(); + + // Initialize arrays for segment ID and shower flag (-1 means no segment assigned) + std::vector point_segment_id(npoints, -1); + std::vector point_flag_shower(npoints, 0); + + // Iterate through all edges (segments) in the graph + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr seg = graph[*eit].segment; + + // Skip if segment is null or doesn't belong to this cluster + if (!seg || seg->cluster() != &cluster) continue; + + // Get the edge index as the segment ID + const auto& edge_bundle = graph[*eit]; + int segment_id = static_cast(edge_bundle.index); + + seg->set_id(segment_id); + + // Check if segment is a shower (either shower trajectory or shower topology) + bool is_shower = seg->flags_any(SegmentFlags::kShowerTrajectory) || + seg->flags_any(SegmentFlags::kShowerTopology); + + // Get the local-to-global index mapping for this segment's point cloud + if (seg->has_global_indices(cloud_name)) { + const auto& global_indices = seg->global_indices(cloud_name); + + // Map each local point to the global cluster point + for (size_t local_idx = 0; local_idx < global_indices.size(); ++local_idx) { + size_t global_idx = global_indices[local_idx]; + + // Validate global index + if (global_idx < npoints) { + point_segment_id[global_idx] = segment_id; + point_flag_shower[global_idx] = is_shower ? 1 : 0; + } + } + } + } + + // Add the arrays to the cluster's default point cloud as named arrays + auto& local_pcs = cluster.local_pcs(); + + // Get or create the default point cloud + // The default point cloud should already exist, but we need to add arrays to it + // We'll add to the root node's local_pcs + auto& default_pc = local_pcs["3d"]; // The default 3D point cloud + + // Add the arrays + using namespace WireCell::PointCloud; + default_pc.add("point_segment_id", Array(point_segment_id)); + default_pc.add("point_flag_shower", Array(point_flag_shower)); +} From 7e91ac604b3abc5fff6cccd3c2ad692edbaabdaf Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Wed, 17 Dec 2025 11:35:19 -0800 Subject: [PATCH 067/111] add sufficient code for transfer information from segment to cluster --- clus/inc/WireCellClus/Facade_Cluster.h | 24 +++++++++ clus/inc/WireCellClus/Facade_ClusterCache.h | 12 +++++ clus/src/Facade_Cluster.cxx | 54 +++++++++++++++++++++ clus/src/NeutrinoPatternBase.cxx | 7 +++ 4 files changed, 97 insertions(+) diff --git a/clus/inc/WireCellClus/Facade_Cluster.h b/clus/inc/WireCellClus/Facade_Cluster.h index 741396e0..05c5ebf6 100644 --- a/clus/inc/WireCellClus/Facade_Cluster.h +++ b/clus/inc/WireCellClus/Facade_Cluster.h @@ -280,6 +280,30 @@ namespace WireCell::Clus::Facade { double charge_cut = 4000.0, bool disable_dead_mix_cell = true) const; + /// Get segment IDs for all points (computed from graph analysis) + /// @return Vector of segment IDs, -1 for unassigned points + std::vector segment_ids() const; + + /// Get shower flags for all points (computed from graph analysis) + /// @return Vector of flags, 1=shower, 0=track + std::vector shower_flags() const; + + /// Get segment ID for a specific point + /// @param point_index Global point index in cluster + /// @return Segment ID or -1 if unassigned + int segment_id(size_t point_index) const; + + /// Get shower flag for a specific point + /// @param point_index Global point index in cluster + /// @return 1 if shower, 0 if track + int shower_flag(size_t point_index) const; + + /// Invalidate cached segment data (IDs and shower flags) + /// Call this before re-computing segment information + void invalidate_segment_data() { + cache().invalidate_segment_data(); + } + // Return vector is size 3 holding vectors of size npoints providing k-d tree coordinate points. diff --git a/clus/inc/WireCellClus/Facade_ClusterCache.h b/clus/inc/WireCellClus/Facade_ClusterCache.h index d819d489..2f01a4ac 100644 --- a/clus/inc/WireCellClus/Facade_ClusterCache.h +++ b/clus/inc/WireCellClus/Facade_ClusterCache.h @@ -74,11 +74,23 @@ namespace WireCell::Clus::Facade { // Set of point indices excluded during graph operations (equivalent to prototype's excluded_points) std::set excluded_points; + // Segment IDs by point index (computed from graph analysis) + std::vector point_segment_ids; + + // Shower flags by point index (computed from graph analysis) + std::vector point_shower_flags; + // Steiner point cloud k-d tree cache mutable std::unique_ptr steiner_kd; mutable decltype(std::declval().get(std::vector{})) steiner_query3d; mutable std::string cached_steiner_pc_name; mutable bool steiner_kd_built{false}; + + // Invalidate segment-related cached data + void invalidate_segment_data() { + point_segment_ids.clear(); + point_shower_flags.clear(); + } }; } diff --git a/clus/src/Facade_Cluster.cxx b/clus/src/Facade_Cluster.cxx index 0b8f2a66..3465d70f 100644 --- a/clus/src/Facade_Cluster.cxx +++ b/clus/src/Facade_Cluster.cxx @@ -829,6 +829,60 @@ WirePlaneId Cluster::wire_plane_id(size_t point_index) const { return WirePlaneId(wpids[point_index]); } +std::vector Cluster::segment_ids() const { + auto& seg_ids = cache().point_segment_ids; + if (seg_ids.empty()) { + auto& lpcs = const_cast(this)->local_pcs(); + auto it = lpcs.find("3d"); + if (it != lpcs.end()) { + auto arr = it->second.get("point_segment_id"); + if (arr) { + auto span = arr->elements(); + seg_ids.assign(span.begin(), span.end()); + } + } + if (seg_ids.empty()) { + seg_ids.resize(npoints(), -1); + } + } + return seg_ids; +} + +std::vector Cluster::shower_flags() const { + auto& flags = cache().point_shower_flags; + if (flags.empty()) { + auto& lpcs = const_cast(this)->local_pcs(); + auto it = lpcs.find("3d"); + if (it != lpcs.end()) { + auto arr = it->second.get("point_flag_shower"); + if (arr) { + auto span = arr->elements(); + flags.assign(span.begin(), span.end()); + } + } + if (flags.empty()) { + flags.resize(npoints(), 0); + } + } + return flags; +} + +int Cluster::segment_id(size_t point_index) const { + const auto& seg_ids = segment_ids(); + if (point_index < seg_ids.size()) { + return seg_ids[point_index]; + } + return -1; +} + +int Cluster::shower_flag(size_t point_index) const { + const auto& flags = shower_flags(); + if (point_index < flags.size()) { + return flags[point_index]; + } + return 0; +} + int Cluster::wire_index(size_t point_index, int plane) const { auto& cache_ref = cache(); diff --git a/clus/src/NeutrinoPatternBase.cxx b/clus/src/NeutrinoPatternBase.cxx index 82298183..40eb23f6 100644 --- a/clus/src/NeutrinoPatternBase.cxx +++ b/clus/src/NeutrinoPatternBase.cxx @@ -1188,6 +1188,9 @@ void PatternAlgorithms::transfer_info_from_segment_to_cluster(Graph& graph, Faca } } + // Invalidate cache before updating arrays + cluster.invalidate_segment_data(); + // Add the arrays to the cluster's default point cloud as named arrays auto& local_pcs = cluster.local_pcs(); @@ -1196,6 +1199,10 @@ void PatternAlgorithms::transfer_info_from_segment_to_cluster(Graph& graph, Faca // We'll add to the root node's local_pcs auto& default_pc = local_pcs["3d"]; // The default 3D point cloud + // Erase old arrays if they exist (allows re-adding) + default_pc.erase("point_segment_id"); + default_pc.erase("point_flag_shower"); + // Add the arrays using namespace WireCell::PointCloud; default_pc.add("point_segment_id", Array(point_segment_id)); From f59a2da8c61d6643025d914ab6be35cff4c19a04 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Thu, 18 Dec 2025 12:31:56 -0800 Subject: [PATCH 068/111] implement determine_direction --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 2 + clus/src/NeutrinoTrackShowerSep.cxx | 57 +++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 5d8322b4..33ba2f74 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -81,6 +81,8 @@ namespace WireCell::Clus::PR { // EM shower related void clustering_points(Graph& graph, Facade::Cluster& cluster, const IDetectorVolumes::pointer& dv, const std::string& cloud_name = "associated_points", double search_range = 1.2*units::cm, double scaling_2d = 0.7); void separate_track_shower(Graph&graph, Facade::Cluster& cluster); + // Direction + void determine_direction(Graph& graph, Facade::Cluster& cluster, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); // vertex related functions diff --git a/clus/src/NeutrinoTrackShowerSep.cxx b/clus/src/NeutrinoTrackShowerSep.cxx index 0f2068b0..8b9ab701 100644 --- a/clus/src/NeutrinoTrackShowerSep.cxx +++ b/clus/src/NeutrinoTrackShowerSep.cxx @@ -40,3 +40,60 @@ void PatternAlgorithms::separate_track_shower(Graph&graph, Facade::Cluster& clus } } } + +void PatternAlgorithms::determine_direction(Graph& graph, Facade::Cluster& cluster, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model) { + // Iterate through all edges (segments) in the graph + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr seg = graph[*eit].segment; + + // Skip if segment is null or doesn't belong to this cluster + if (!seg || seg->cluster() != &cluster) continue; + + // Get the two vertices of this segment + auto [start_v, end_v] = find_vertices(graph, seg); + if (!start_v || !end_v) { + std::cout << "Error in finding vertices for a segment" << std::endl; + continue; + } + + // Check if vertices match the segment endpoints (start_v should be at front, end_v at back) + const auto& wcpts = seg->wcpts(); + if (wcpts.size() < 2) continue; + + auto front_pt = wcpts.front().point; + auto back_pt = wcpts.back().point; + + // Determine which vertex is start and which is end based on point positions + double dis_sv_front = ray_length(Ray{start_v->wcpt().point, front_pt}); + double dis_sv_back = ray_length(Ray{start_v->wcpt().point, back_pt}); + + if (dis_sv_front > dis_sv_back) { + std::swap(start_v, end_v); + } + + // Count number of segments connected to each vertex + int start_n = 0, end_n = 0; + if (start_v->descriptor_valid()) { + start_n = boost::degree(start_v->get_descriptor(), graph); + } + if (end_v->descriptor_valid()) { + end_n = boost::degree(end_v->get_descriptor(), graph); + } + + bool flag_print = false; + // if (seg->cluster() == main_cluster) flag_print = true; + + if (seg->flags_any(SegmentFlags::kShowerTrajectory)) { + // Trajectory shower + segment_determine_shower_direction_trajectory(seg, start_n, end_n, particle_data, recomb_model, 43000/units::cm, flag_print); + } else if (seg->flags_any(SegmentFlags::kShowerTopology)) { + // Topology shower + segment_determine_shower_direction(seg, particle_data, recomb_model); + } else { + // Track + segment_determine_dir_track(seg, start_n, end_n, particle_data, recomb_model, 43000/units::cm, flag_print); + } + } +} + From b4a159aa69739242d239026c4ca02c5429e91b0a Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Thu, 18 Dec 2025 13:05:35 -0800 Subject: [PATCH 069/111] implement examine_good_tracks --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 2 + clus/src/NeutrinoTrackShowerSep.cxx | 160 ++++++++++++++++++++ 2 files changed, 162 insertions(+) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 33ba2f74..96445574 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -83,6 +83,8 @@ namespace WireCell::Clus::PR { void separate_track_shower(Graph&graph, Facade::Cluster& cluster); // Direction void determine_direction(Graph& graph, Facade::Cluster& cluster, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); + std::pair calculate_num_daughter_showers(Graph& graph, VertexPtr vertex, SegmentPtr segment, bool flag_count_shower = true); + void examine_good_tracks(Graph& graph, Facade::Cluster& cluster, const Clus::ParticleDataSet::pointer& particle_data); // vertex related functions diff --git a/clus/src/NeutrinoTrackShowerSep.cxx b/clus/src/NeutrinoTrackShowerSep.cxx index 8b9ab701..9a0c3439 100644 --- a/clus/src/NeutrinoTrackShowerSep.cxx +++ b/clus/src/NeutrinoTrackShowerSep.cxx @@ -97,3 +97,163 @@ void PatternAlgorithms::determine_direction(Graph& graph, Facade::Cluster& clust } } +std::pair PatternAlgorithms::calculate_num_daughter_showers(Graph& graph, VertexPtr vertex, SegmentPtr segment, bool flag_count_shower) { + int number_showers = 0; + double acc_length = 0; + + std::set used_vertices; + std::set used_segments; + + std::vector> segments_to_be_examined; + segments_to_be_examined.push_back(std::make_pair(vertex, segment)); + used_vertices.insert(vertex); + + while(segments_to_be_examined.size() > 0) { + std::vector> temp_segments; + for (auto it = segments_to_be_examined.begin(); it != segments_to_be_examined.end(); it++) { + VertexPtr prev_vtx = it->first; + SegmentPtr current_sg = it->second; + + if (used_segments.find(current_sg) != used_segments.end()) continue; // looked at it before + + // Check if segment is a shower (has kShowerTrajectory or kShowerTopology flags) + bool is_shower = current_sg->flags_any(SegmentFlags::kShowerTrajectory) || + current_sg->flags_any(SegmentFlags::kShowerTopology); + + if (is_shower || (!flag_count_shower)) { + number_showers++; + acc_length += segment_track_length(current_sg); + } + used_segments.insert(current_sg); + + VertexPtr curr_vertex = find_other_vertex(graph, current_sg, prev_vtx); + if (used_vertices.find(curr_vertex) != used_vertices.end()) continue; + + // Get all segments connected to curr_vertex + if (curr_vertex && curr_vertex->descriptor_valid()) { + auto vd = curr_vertex->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + for (auto eit = edge_range.first; eit != edge_range.second; ++eit) { + SegmentPtr seg = graph[*eit].segment; + if (seg) { + temp_segments.push_back(std::make_pair(curr_vertex, seg)); + } + } + } + used_vertices.insert(curr_vertex); + } + segments_to_be_examined = temp_segments; + } + + return std::make_pair(number_showers, acc_length); +} + +void PatternAlgorithms::examine_good_tracks(Graph& graph, Facade::Cluster& cluster, const Clus::ParticleDataSet::pointer& particle_data) { + // Iterate through all edges (segments) in the graph + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr sg = graph[*eit].segment; + + // Skip if segment is null or doesn't belong to this cluster + if (!sg || sg->cluster() != &cluster) continue; + + // Skip if segment is a shower + if (sg->flags_any(SegmentFlags::kShowerTrajectory) || sg->flags_any(SegmentFlags::kShowerTopology)) continue; + + // Skip if no direction or weak direction + if (sg->dirsign() == 0 || sg->dir_weak()) continue; + + // Get the two vertices of this segment + auto [vertex1, vertex2] = find_vertices(graph, sg); + if (!vertex1 || !vertex2) continue; + + // Determine start and end vertices based on segment direction + VertexPtr start_vertex = nullptr, end_vertex = nullptr; + const auto& wcpts = sg->wcpts(); + if (wcpts.size() < 2) continue; + + auto front_pt = wcpts.front().point; + auto back_pt = wcpts.back().point; + + if (sg->dirsign() == 1) { + // Direction is forward (from front to back) + double dis1_front = ray_length(Ray{vertex1->wcpt().point, front_pt}); + double dis1_back = ray_length(Ray{vertex1->wcpt().point, back_pt}); + if (dis1_front < dis1_back) { + start_vertex = vertex1; + end_vertex = vertex2; + } else { + start_vertex = vertex2; + end_vertex = vertex1; + } + } else if (sg->dirsign() == -1) { + // Direction is backward (from back to front) + double dis1_front = ray_length(Ray{vertex1->wcpt().point, front_pt}); + double dis1_back = ray_length(Ray{vertex1->wcpt().point, back_pt}); + if (dis1_front < dis1_back) { + start_vertex = vertex2; + end_vertex = vertex1; + } else { + start_vertex = vertex1; + end_vertex = vertex2; + } + } + + if (!start_vertex || !end_vertex) continue; + + // Calculate number of daughter showers + auto result_pair = calculate_num_daughter_showers(graph, start_vertex, sg); + int num_daughter_showers = result_pair.first; + double length_daughter_showers = result_pair.second; + + // Calculate maximum angle between this segment and others at end_vertex + double max_angle = 0; + WireCell::Point end_pt = end_vertex->fit().valid() ? end_vertex->fit().point : end_vertex->wcpt().point; + WireCell::Vector dir1 = segment_cal_dir_3vector(sg, end_pt, 15*units::cm); + WireCell::Vector drift_dir(1, 0, 0); + double min_para_angle = 1e9; + + // Get all segments connected to end_vertex + if (end_vertex->descriptor_valid()) { + auto vd = end_vertex->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + for (auto eit2 = edge_range.first; eit2 != edge_range.second; ++eit2) { + SegmentPtr sg1 = graph[*eit2].segment; + if (!sg1 || sg1 == sg) continue; + + WireCell::Vector dir2 = segment_cal_dir_3vector(sg1, end_pt, 15*units::cm); + double angle = std::acos(std::min(1.0, std::max(-1.0, dir1.dot(dir2) / (dir1.magnitude() * dir2.magnitude())))) / 3.1415926 * 180.0; + if (angle > max_angle) max_angle = angle; + + angle = std::fabs(std::acos(std::min(1.0, std::max(-1.0, drift_dir.dot(dir2) / (drift_dir.magnitude() * dir2.magnitude())))) / 3.1415926 * 180.0 - 90.0); + if (angle < min_para_angle) min_para_angle = angle; + } + } + + // Check if this track should be reclassified as an electron shower + double drift_angle = std::fabs(std::acos(std::min(1.0, std::max(-1.0, drift_dir.dot(dir1) / (drift_dir.magnitude() * dir1.magnitude())))) / 3.1415926 * 180.0 - 90.0); + double length = segment_track_length(sg); + + if ((num_daughter_showers >= 4 || (length_daughter_showers > 50*units::cm && num_daughter_showers >= 2)) && + (max_angle > 155 || (drift_angle < 15 && min_para_angle < 15 && min_para_angle + drift_angle < 25)) && + length < 15*units::cm) { + + // Reclassify as electron (PDG 11) + auto pinfo = std::make_shared( + 11, // electron PDG + particle_data->get_particle_mass(11), // electron mass + particle_data->pdg_to_name(11), // "e-" + WireCell::D4Vector(0, 0, 0, 0) // zero 4-momentum (will be recalculated) + ); + sg->particle_info(pinfo); + + // Reset direction and mark as weak + sg->dirsign(0); + sg->dir_weak(true); + } + + // Debug output (commented out) + // std::cout << sg->get_id() << " " << sg->particle_type() << " " << num_daughter_showers << " " + // << length/units::cm << " " << max_angle << " " << min_para_angle << " " << drift_angle << std::endl; + } +} \ No newline at end of file From 6901400b51f445a0225bf2cfa490bfd27cc0aab9 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Thu, 18 Dec 2025 13:15:29 -0800 Subject: [PATCH 070/111] implement examine_good_tracks --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 3 + clus/src/NeutrinoTrackShowerSep.cxx | 132 +++++++++++++++++++- 2 files changed, 134 insertions(+), 1 deletion(-) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 96445574..0db1e267 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -85,6 +85,9 @@ namespace WireCell::Clus::PR { void determine_direction(Graph& graph, Facade::Cluster& cluster, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); std::pair calculate_num_daughter_showers(Graph& graph, VertexPtr vertex, SegmentPtr segment, bool flag_count_shower = true); void examine_good_tracks(Graph& graph, Facade::Cluster& cluster, const Clus::ParticleDataSet::pointer& particle_data); + // about fix maps + void fix_maps_multiple_tracks_in(Graph& graph, Facade::Cluster& cluster); + void fix_maps_shower_in_track_out(Graph& graph, Facade::Cluster& cluster); // vertex related functions diff --git a/clus/src/NeutrinoTrackShowerSep.cxx b/clus/src/NeutrinoTrackShowerSep.cxx index 9a0c3439..47bfc683 100644 --- a/clus/src/NeutrinoTrackShowerSep.cxx +++ b/clus/src/NeutrinoTrackShowerSep.cxx @@ -256,4 +256,134 @@ void PatternAlgorithms::examine_good_tracks(Graph& graph, Facade::Cluster& clust // std::cout << sg->get_id() << " " << sg->particle_type() << " " << num_daughter_showers << " " // << length/units::cm << " " << max_angle << " " << min_para_angle << " " << drift_angle << std::endl; } -} \ No newline at end of file +} + +void PatternAlgorithms::fix_maps_multiple_tracks_in(Graph& graph, Facade::Cluster& cluster){ + // Iterate through all vertices in the graph + auto [vbegin, vend] = boost::vertices(graph); + for (auto vit = vbegin; vit != vend; ++vit) { + VertexPtr vtx = graph[*vit].vertex; + + // Skip if vertex is null or doesn't belong to this cluster + if (!vtx || !vtx->cluster() || vtx->cluster() != &cluster) continue; + + // Check how many segments are connected to this vertex + if (!vtx->descriptor_valid()) continue; + auto vd = vtx->get_descriptor(); + if (boost::degree(vd, graph) <= 1) continue; + + int n_in = 0; + int n_in_shower = 0; + std::vector in_tracks; + + // Get vertex position + WireCell::Point vtx_point = vtx->wcpt().point; + + // Iterate through all segments connected to this vertex + auto edge_range = boost::out_edges(vd, graph); + for (auto eit = edge_range.first; eit != edge_range.second; ++eit) { + SegmentPtr sg = graph[*eit].segment; + if (!sg) continue; + + // Determine if this vertex is at the front or back of the segment + const auto& wcpts = sg->wcpts(); + if (wcpts.size() < 2) continue; + + auto front_pt = wcpts.front().point; + auto back_pt = wcpts.back().point; + + double dis_front = ray_length(Ray{vtx_point, front_pt}); + double dis_back = ray_length(Ray{vtx_point, back_pt}); + + bool flag_start = (dis_front < dis_back); // vertex is at the front of segment + + // Check if this segment is pointing "in" to the vertex + // "in" means: (at front and direction is -1) OR (at back and direction is 1) + if ((flag_start && sg->dirsign() == -1) || (!flag_start && sg->dirsign() == 1)) { + n_in++; + + // Check if it's a shower + if (sg->flags_any(SegmentFlags::kShowerTrajectory) || sg->flags_any(SegmentFlags::kShowerTopology)) { + n_in_shower++; + } else { + in_tracks.push_back(sg); + } + } + } + + // If there are multiple incoming tracks (not all showers), reset their directions + if (n_in > 1 && n_in != n_in_shower) { + for (auto it1 = in_tracks.begin(); it1 != in_tracks.end(); it1++) { + (*it1)->dirsign(0); + (*it1)->dir_weak(true); + } + } + } +} + +void PatternAlgorithms::fix_maps_shower_in_track_out(Graph& graph, Facade::Cluster& cluster){ + // Iterate through all vertices in the graph + auto [vbegin, vend] = boost::vertices(graph); + for (auto vit = vbegin; vit != vend; ++vit) { + VertexPtr vtx = graph[*vit].vertex; + + // Skip if vertex is null or doesn't belong to this cluster + if (!vtx || !vtx->cluster() || vtx->cluster() != &cluster) continue; + + // Check how many segments are connected to this vertex + if (!vtx->descriptor_valid()) continue; + auto vd = vtx->get_descriptor(); + if (boost::degree(vd, graph) <= 1) continue; + + std::vector in_showers; + bool flag_turn_shower_dir = false; + + // Get vertex position + WireCell::Point vtx_point = vtx->wcpt().point; + + // Iterate through all segments connected to this vertex + auto edge_range = boost::out_edges(vd, graph); + for (auto eit = edge_range.first; eit != edge_range.second; ++eit) { + SegmentPtr sg = graph[*eit].segment; + if (!sg) continue; + + // Determine if this vertex is at the front or back of the segment + const auto& wcpts = sg->wcpts(); + if (wcpts.size() < 2) continue; + + auto front_pt = wcpts.front().point; + auto back_pt = wcpts.back().point; + + double dis_front = ray_length(Ray{vtx_point, front_pt}); + double dis_back = ray_length(Ray{vtx_point, back_pt}); + + bool flag_start = (dis_front < dis_back); // vertex is at the front of segment + + // Check if segment is a shower + bool is_shower = sg->flags_any(SegmentFlags::kShowerTrajectory) || + sg->flags_any(SegmentFlags::kShowerTopology); + + // Check if this is an "incoming" segment (pointing into vertex) + if ((flag_start && sg->dirsign() == -1) || (!flag_start && sg->dirsign() == 1)) { + if (is_shower) { + in_showers.push_back(sg); + } + } + // Check if this is an "outgoing" segment (pointing away from vertex) + else if ((flag_start && sg->dirsign() == 1) || (!flag_start && sg->dirsign() == -1)) { + // If it's an outgoing non-shower track with strong direction + if (!is_shower && !sg->dir_weak()) { + flag_turn_shower_dir = true; + } + } + } + + // If there's a strong outgoing track and incoming showers, flip shower directions + if (flag_turn_shower_dir) { + for (auto it1 = in_showers.begin(); it1 != in_showers.end(); it1++) { + (*it1)->dirsign((*it1)->dirsign() * (-1)); + (*it1)->dir_weak(true); + } + } + } +} From e443a5d73cd2082212af27f7ff2edfe1f92fb142 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Thu, 18 Dec 2025 13:29:16 -0800 Subject: [PATCH 071/111] implement another improve maps function --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 2 + clus/src/NeutrinoTrackShowerSep.cxx | 252 ++++++++++++++++++++ 2 files changed, 254 insertions(+) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 0db1e267..04a05ae1 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -88,6 +88,8 @@ namespace WireCell::Clus::PR { // about fix maps void fix_maps_multiple_tracks_in(Graph& graph, Facade::Cluster& cluster); void fix_maps_shower_in_track_out(Graph& graph, Facade::Cluster& cluster); + void improve_maps_one_in(Graph& graph, Facade::Cluster& cluster, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model, bool flag_strong_check = true); + void improve_maps_shower_in_track_out(Graph& graph, Facade::Cluster& cluster, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model, bool flag_strong_check = true); // vertex related functions diff --git a/clus/src/NeutrinoTrackShowerSep.cxx b/clus/src/NeutrinoTrackShowerSep.cxx index 47bfc683..78302143 100644 --- a/clus/src/NeutrinoTrackShowerSep.cxx +++ b/clus/src/NeutrinoTrackShowerSep.cxx @@ -387,3 +387,255 @@ void PatternAlgorithms::fix_maps_shower_in_track_out(Graph& graph, Facade::Clust } } } + +void PatternAlgorithms::improve_maps_one_in(Graph& graph, Facade::Cluster& cluster, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model, bool flag_strong_check){ + bool flag_update = true; + std::set used_vertices; + std::set used_segments; + + while(flag_update) { + flag_update = false; + + // Iterate through all vertices in the graph + auto [vbegin, vend] = boost::vertices(graph); + for (auto vit = vbegin; vit != vend; ++vit) { + VertexPtr vtx = graph[*vit].vertex; + + // Skip if vertex is null or doesn't belong to this cluster + if (!vtx || !vtx->cluster() || vtx->cluster() != &cluster) continue; + + // Check how many segments are connected to this vertex + if (!vtx->descriptor_valid()) continue; + auto vd = vtx->get_descriptor(); + if (boost::degree(vd, graph) <= 1) continue; + + // Skip if already processed + if (used_vertices.find(vtx) != used_vertices.end()) continue; + + int n_in = 0; + std::map map_sg_dir; // segment -> flag_start + + // Get vertex position + WireCell::Point vtx_point = vtx->wcpt().point; + + // Iterate through all segments connected to this vertex + auto edge_range = boost::out_edges(vd, graph); + for (auto eit = edge_range.first; eit != edge_range.second; ++eit) { + SegmentPtr sg = graph[*eit].segment; + if (!sg) continue; + + // Skip if segment already processed + if (used_segments.find(sg) != used_segments.end()) continue; + + // Determine if this vertex is at the front or back of the segment + const auto& wcpts = sg->wcpts(); + if (wcpts.size() < 2) continue; + + auto front_pt = wcpts.front().point; + auto back_pt = wcpts.back().point; + + double dis_front = ray_length(Ray{vtx_point, front_pt}); + double dis_back = ray_length(Ray{vtx_point, back_pt}); + + bool flag_start = (dis_front < dis_back); // vertex is at the front of segment + + // Check if this is an "incoming" segment (pointing into vertex) + if ((flag_start && sg->dirsign() == -1) || (!flag_start && sg->dirsign() == 1)) { + if (flag_strong_check) { + // Only count if direction is strong + if (!sg->dir_weak()) n_in++; + } else { + n_in++; + } + } + + // Collect segments with no or weak direction + if (sg->dirsign() == 0 || sg->dir_weak()) { + map_sg_dir[sg] = flag_start; + } + } + + // If no segments to change direction, mark vertex as used + if (map_sg_dir.size() == 0) { + used_vertices.insert(vtx); + } + + // If there are incoming segments, set all weak/no-direction segments to point out + if (n_in > 0) { + for (auto it1 = map_sg_dir.begin(); it1 != map_sg_dir.end(); it1++) { + SegmentPtr sg = it1->first; + bool flag_start = it1->second; + + // Set direction to point away from vertex + if (flag_start) { + sg->dirsign(1); // at front, point forward + } else { + sg->dirsign(-1); // at back, point backward + } + + // Recalculate 4-momentum if particle info exists + if (sg->has_particle_info()) { + int pdg_code = sg->particle_info()->pdg(); + auto four_momentum = segment_cal_4mom(sg, pdg_code, particle_data, recomb_model); + + // Update particle info with new 4-momentum + auto pinfo = std::make_shared( + pdg_code, + particle_data->get_particle_mass(pdg_code), + particle_data->pdg_to_name(pdg_code), + four_momentum + ); + sg->particle_info(pinfo); + } + + sg->dir_weak(true); + used_segments.insert(sg); + flag_update = true; + } + used_vertices.insert(vtx); + } + } + } +} + +void PatternAlgorithms::improve_maps_shower_in_track_out(Graph& graph, Facade::Cluster& cluster, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model, bool flag_strong_check){ + bool flag_update = true; + std::set used_vertices; + std::set used_segments; + + while(flag_update) { + flag_update = false; + + // Iterate through all vertices in the graph + auto [vbegin, vend] = boost::vertices(graph); + for (auto vit = vbegin; vit != vend; ++vit) { + VertexPtr vtx = graph[*vit].vertex; + + // Skip if vertex is null or doesn't belong to this cluster + if (!vtx || !vtx->cluster() || vtx->cluster() != &cluster) continue; + + // Check how many segments are connected to this vertex + if (!vtx->descriptor_valid()) continue; + auto vd = vtx->get_descriptor(); + if (boost::degree(vd, graph) <= 1) continue; + + // Skip if already processed + if (used_vertices.find(vtx) != used_vertices.end()) continue; + + // int n_in = 0; + int n_in_shower = 0; + std::vector out_tracks; + std::map map_no_dir_segments; // segment -> flag_start + + // Get vertex position + WireCell::Point vtx_point = vtx->wcpt().point; + + // Iterate through all segments connected to this vertex + auto edge_range = boost::out_edges(vd, graph); + for (auto eit = edge_range.first; eit != edge_range.second; ++eit) { + SegmentPtr sg = graph[*eit].segment; + if (!sg) continue; + + // Determine if this vertex is at the front or back of the segment + const auto& wcpts = sg->wcpts(); + if (wcpts.size() < 2) continue; + + auto front_pt = wcpts.front().point; + auto back_pt = wcpts.back().point; + + double dis_front = ray_length(Ray{vtx_point, front_pt}); + double dis_back = ray_length(Ray{vtx_point, back_pt}); + + bool flag_start = (dis_front < dis_back); // vertex is at the front of segment + + bool is_shower = sg->flags_any(SegmentFlags::kShowerTrajectory) || + sg->flags_any(SegmentFlags::kShowerTopology); + + // Check if this is an "incoming" segment (pointing into vertex) + if ((flag_start && sg->dirsign() == -1) || (!flag_start && sg->dirsign() == 1)) { + // n_in++; + if (is_shower) { + n_in_shower++; + } + } + // Check if this is an "outgoing" segment (pointing away from vertex) + else if ((flag_start && sg->dirsign() == 1) || (!flag_start && sg->dirsign() == -1)) { + if (!is_shower) { + // Check if it's weak or has no particle type + bool no_particle_type = !sg->has_particle_info() || sg->particle_info()->pdg() == 0; + if (sg->dir_weak() || (no_particle_type && !flag_strong_check)) { + out_tracks.push_back(sg); + } + } + } + // Segment with no direction + else if (sg->dirsign() == 0) { + map_no_dir_segments[sg] = flag_start; + } + } + + // If there are incoming showers and outgoing tracks or no-direction segments + if (n_in_shower > 0 && (out_tracks.size() > 0 || map_no_dir_segments.size() > 0)) { + // Reclassify outgoing tracks as electrons + for (auto it1 = out_tracks.begin(); it1 != out_tracks.end(); it1++) { + SegmentPtr sg1 = *it1; + + // Set as electron (PDG 11) + int pdg_code = 11; + auto four_momentum = WireCell::D4Vector(0, 0, 0, 0); + + // Recalculate 4-momentum if segment has valid energy + if (sg1->has_particle_info() && sg1->particle_info()->energy() > 0) { + four_momentum = segment_cal_4mom(sg1, pdg_code, particle_data, recomb_model); + } + + auto pinfo = std::make_shared( + pdg_code, + particle_data->get_particle_mass(pdg_code), + particle_data->pdg_to_name(pdg_code), + four_momentum + ); + sg1->particle_info(pinfo); + sg1->dirsign(0); + + flag_update = true; + } + + // Process no-direction segments + for (auto it1 = map_no_dir_segments.begin(); it1 != map_no_dir_segments.end(); it1++) { + SegmentPtr sg1 = it1->first; + if (used_segments.find(sg1) != used_segments.end()) continue; + + // If it's not already a shower, set as electron + bool is_shower = sg1->flags_any(SegmentFlags::kShowerTrajectory) || + sg1->flags_any(SegmentFlags::kShowerTopology); + + if (!is_shower) { + int pdg_code = 11; + auto four_momentum = WireCell::D4Vector(0, 0, 0, 0); + + // Recalculate 4-momentum if segment has valid energy + if (sg1->has_particle_info() && sg1->particle_info()->energy() > 0) { + four_momentum = segment_cal_4mom(sg1, pdg_code, particle_data, recomb_model); + } + + auto pinfo = std::make_shared( + pdg_code, + particle_data->get_particle_mass(pdg_code), + particle_data->pdg_to_name(pdg_code), + four_momentum + ); + sg1->particle_info(pinfo); + } + + sg1->dir_weak(true); + used_segments.insert(sg1); + flag_update = true; + } + } + + used_vertices.insert(vtx); + } + } +} + From c1d02a0fd8975a1583e4a478631a5bd1a43db975 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Thu, 18 Dec 2025 14:51:24 -0800 Subject: [PATCH 072/111] implement the examine_maps function --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 8 +- clus/src/NeutrinoPatternBase.cxx | 66 ++ clus/src/NeutrinoTrackShowerSep.cxx | 738 ++++++++++++++++++++ 3 files changed, 811 insertions(+), 1 deletion(-) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 04a05ae1..300d311c 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -90,7 +90,10 @@ namespace WireCell::Clus::PR { void fix_maps_shower_in_track_out(Graph& graph, Facade::Cluster& cluster); void improve_maps_one_in(Graph& graph, Facade::Cluster& cluster, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model, bool flag_strong_check = true); void improve_maps_shower_in_track_out(Graph& graph, Facade::Cluster& cluster, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model, bool flag_strong_check = true); - + void improve_maps_no_dir_tracks(Graph& graph, Facade::Cluster& cluster, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); + void improve_maps_multiple_tracks_in(Graph& graph, Facade::Cluster& cluster, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); + void judge_no_dir_tracks_close_to_showers(Graph& graph, Facade::Cluster& cluster, const Clus::ParticleDataSet::pointer& particle_data, IDetectorVolumes::pointer dv); + bool examine_maps(Graph&graph, Facade::Cluster& cluster); // vertex related functions bool search_for_vertex_activities(Graph& graph, VertexPtr vertex, std::set& segments_set, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, double search_range = 1.5*units::cm); @@ -98,5 +101,8 @@ namespace WireCell::Clus::PR { // global information transfer void transfer_info_from_segment_to_cluster(Graph& graph, Facade::Cluster& cluster, const std::string& cloud_name = "associated_points"); + // print information + void print_segs_info(Graph& graph, Facade::Cluster& cluster, VertexPtr vertex= nullptr); + }; } diff --git a/clus/src/NeutrinoPatternBase.cxx b/clus/src/NeutrinoPatternBase.cxx index 40eb23f6..208dc832 100644 --- a/clus/src/NeutrinoPatternBase.cxx +++ b/clus/src/NeutrinoPatternBase.cxx @@ -1208,3 +1208,69 @@ void PatternAlgorithms::transfer_info_from_segment_to_cluster(Graph& graph, Faca default_pc.add("point_segment_id", Array(point_segment_id)); default_pc.add("point_flag_shower", Array(point_flag_shower)); } + + +void PatternAlgorithms::print_segs_info(Graph& graph, Facade::Cluster& cluster, VertexPtr vertex){ + // Iterate through all segments in the graph + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr sg = graph[*eit].segment; + + // Skip if segment is null or doesn't belong to this cluster + if (!sg || sg->cluster() != &cluster) continue; + + // If a specific vertex is provided, check if segment is connected to it + if (vertex != nullptr) { + auto [v1, v2] = find_vertices(graph, sg); + if (v1 != vertex && v2 != vertex) continue; + } + + // Determine if segment is "in" or "out" relative to the specific vertex + int in_vertex = 0; // 0: no direction, -1: in, 1: out + + if (vertex != nullptr) { + const auto& wcpts = sg->wcpts(); + if (wcpts.size() < 2) continue; + + WireCell::Point vtx_point = vertex->wcpt().point; + auto front_pt = wcpts.front().point; + auto back_pt = wcpts.back().point; + + double dis_front = ray_length(Ray{vtx_point, front_pt}); + double dis_back = ray_length(Ray{vtx_point, back_pt}); + + bool flag_start = (dis_front < dis_back); // vertex is at the front of segment + + // Check if segment points into or out of the vertex + if ((flag_start && sg->dirsign() == -1) || (!flag_start && sg->dirsign() == 1)) { + in_vertex = -1; // pointing into vertex + } else if ((flag_start && sg->dirsign() == 1) || (!flag_start && sg->dirsign() == -1)) { + in_vertex = 1; // pointing out of vertex + } + } + + // Get segment properties + int seg_id = sg->id(); + double length = segment_track_length(sg) / units::cm; + int flag_dir = sg->dirsign(); + int particle_type = sg->has_particle_info() ? sg->particle_info()->pdg() : 0; + double particle_mass = sg->has_particle_info() ? sg->particle_info()->mass() / units::MeV : 0; + double kinetic_energy = sg->has_particle_info() ? (sg->particle_info()->energy() - sg->particle_info()->mass()) / units::MeV : 0; + bool is_dir_weak = sg->dir_weak(); + + // Determine segment type and print + if (sg->flags_any(SegmentFlags::kShowerTopology)) { + std::cout << seg_id << " " << length << " S_topo " << flag_dir << " " + << particle_type << " " << particle_mass << " " << kinetic_energy << " " + << is_dir_weak << " " << in_vertex << std::endl; + } else if (sg->flags_any(SegmentFlags::kShowerTrajectory)) { + std::cout << seg_id << " " << length << " S_traj " << flag_dir << " " + << particle_type << " " << particle_mass << " " << kinetic_energy << " " + << is_dir_weak << " " << in_vertex << std::endl; + } else { + std::cout << seg_id << " " << length << " Track " << flag_dir << " " + << particle_type << " " << particle_mass << " " << kinetic_energy << " " + << is_dir_weak << " " << in_vertex << std::endl; + } + } +} diff --git a/clus/src/NeutrinoTrackShowerSep.cxx b/clus/src/NeutrinoTrackShowerSep.cxx index 78302143..77b99e5d 100644 --- a/clus/src/NeutrinoTrackShowerSep.cxx +++ b/clus/src/NeutrinoTrackShowerSep.cxx @@ -639,3 +639,741 @@ void PatternAlgorithms::improve_maps_shower_in_track_out(Graph& graph, Facade::C } } +void PatternAlgorithms::improve_maps_no_dir_tracks(Graph& graph, Facade::Cluster& cluster, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model){ + WireCell::Vector drift_dir(1, 0, 0); + bool flag_update = true; + + while(flag_update) { + flag_update = false; + + // Iterate through all edges (segments) in the graph + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr sg = graph[*eit].segment; + + // Skip if segment is null or doesn't belong to this cluster + if (!sg || !sg->cluster() || sg->cluster() != &cluster) continue; + + // Skip showers + bool is_shower = sg->flags_any(SegmentFlags::kShowerTrajectory) || + sg->flags_any(SegmentFlags::kShowerTopology); + if (is_shower) continue; + + double length = segment_track_length(sg); + + // Check if segment has no direction, weak direction, or is a proton + int pdg = sg->has_particle_info() ? sg->particle_info()->pdg() : 0; + if (sg->dirsign() == 0 || sg->dir_weak() || std::abs(pdg) == 2212) { + + auto two_vertices = find_vertices(graph, sg); + if (!two_vertices.first || !two_vertices.second) continue; + + int nshowers[2] = {0, 0}; + int n_in[2] = {0, 0}; + int nmuons[2] = {0, 0}; + int nprotons[2] = {0, 0}; + + // Get vertex descriptors + if (!two_vertices.first->descriptor_valid() || !two_vertices.second->descriptor_valid()) continue; + auto vd1 = two_vertices.first->get_descriptor(); + auto vd2 = two_vertices.second->get_descriptor(); + + WireCell::Point vtx1_pt = two_vertices.first->wcpt().point; + WireCell::Point vtx2_pt = two_vertices.second->wcpt().point; + + // Count segments at first vertex + auto edge_range1 = boost::out_edges(vd1, graph); + for (auto e_it = edge_range1.first; e_it != edge_range1.second; ++e_it) { + SegmentPtr sg1 = graph[*e_it].segment; + if (!sg1) continue; + + const auto& wcpts = sg1->wcpts(); + if (wcpts.size() < 2) continue; + + bool is_shower1 = sg1->flags_any(SegmentFlags::kShowerTrajectory) || + sg1->flags_any(SegmentFlags::kShowerTopology); + if (is_shower1) nshowers[0]++; + + auto front_pt = wcpts.front().point; + auto back_pt = wcpts.back().point; + double dis_front = ray_length(Ray{vtx1_pt, front_pt}); + double dis_back = ray_length(Ray{vtx1_pt, back_pt}); + bool flag_start = (dis_front < dis_back); + + if ((flag_start && sg1->dirsign() == -1) || (!flag_start && sg1->dirsign() == 1)) { + n_in[0]++; + } + + int pdg1 = sg1->has_particle_info() ? sg1->particle_info()->pdg() : 0; + if (std::abs(pdg1) == 13) nmuons[0]++; + if (std::abs(pdg1) == 2212) nprotons[0]++; + } + + // Count segments at second vertex + auto edge_range2 = boost::out_edges(vd2, graph); + for (auto e_it = edge_range2.first; e_it != edge_range2.second; ++e_it) { + SegmentPtr sg1 = graph[*e_it].segment; + if (!sg1) continue; + + const auto& wcpts = sg1->wcpts(); + if (wcpts.size() < 2) continue; + + bool is_shower1 = sg1->flags_any(SegmentFlags::kShowerTrajectory) || + sg1->flags_any(SegmentFlags::kShowerTopology); + if (is_shower1) nshowers[1]++; + + auto front_pt = wcpts.front().point; + auto back_pt = wcpts.back().point; + double dis_front = ray_length(Ray{vtx2_pt, front_pt}); + double dis_back = ray_length(Ray{vtx2_pt, back_pt}); + bool flag_start = (dis_front < dis_back); + + if ((flag_start && sg1->dirsign() == -1) || (!flag_start && sg1->dirsign() == 1)) { + n_in[1]++; + } + + int pdg1 = sg1->has_particle_info() ? sg1->particle_info()->pdg() : 0; + if (std::abs(pdg1) == 13) nmuons[1]++; + if (std::abs(pdg1) == 2212) nprotons[1]++; + } + + int nvtx1_segs = boost::degree(vd1, graph); + int nvtx2_segs = boost::degree(vd2, graph); + + // Case A: Many showers and very short track + if ((nshowers[0] + nshowers[1] > 2 && length < 5*units::cm) || + (nshowers[0]+1 == nvtx1_segs && nshowers[1]+1 == nvtx2_segs && + nshowers[0] > 0 && nshowers[1] > 0 && length < 5*units::cm)) { + + int pdg_code = 11; + auto four_momentum = WireCell::D4Vector(0, 0, 0, 0); + if (sg->has_particle_info() && sg->particle_info()->energy() > 0) { + four_momentum = segment_cal_4mom(sg, pdg_code, particle_data, recomb_model); + } + auto pinfo = std::make_shared( + pdg_code, + particle_data->get_particle_mass(pdg_code), + particle_data->pdg_to_name(pdg_code), + four_momentum + ); + sg->particle_info(pinfo); + flag_update = true; + } + // Case C & D: First/second vertex all showers except current segment (proton) + else if (nshowers[0]+1 == nvtx1_segs && nshowers[0] >= 2 && pdg == 2212) { + WireCell::Vector v1 = segment_cal_dir_3vector(sg, vtx1_pt, 5*units::cm); + double min_angle = 180; + + for (auto e_it = edge_range1.first; e_it != edge_range1.second; ++e_it) { + SegmentPtr sg2 = graph[*e_it].segment; + if (!sg2 || sg2 == sg) continue; + WireCell::Vector v2 = segment_cal_dir_3vector(sg2, vtx1_pt, 5*units::cm); + double angle = std::abs(v1.angle(v2) / 3.14159265 * 180.0 - 180.0); + if (angle < min_angle) min_angle = angle; + } + + double dQ_dx_rms = segment_rms_dQ_dx(sg); + + if ((dQ_dx_rms > 1.0 * (43e3/units::cm) && min_angle < 40) || + (dQ_dx_rms > 0.75 * (43e3/units::cm) && min_angle < 30) || + (dQ_dx_rms > 0.4 * (43e3/units::cm) && min_angle < 15)) { + + const auto& wcpts = sg->wcpts(); + auto front_pt = wcpts.front().point; + auto back_pt = wcpts.back().point; + double dis_front = ray_length(Ray{vtx1_pt, front_pt}); + double dis_back = ray_length(Ray{vtx1_pt, back_pt}); + bool flag_start = (dis_front < dis_back); + + if (flag_start) + sg->dirsign(-1); + else + sg->dirsign(1); + + int pdg_code = 11; + auto four_momentum = WireCell::D4Vector(0, 0, 0, 0); + if (sg->has_particle_info() && sg->particle_info()->energy() > 0) { + four_momentum = segment_cal_4mom(sg, pdg_code, particle_data, recomb_model); + } + auto pinfo = std::make_shared( + pdg_code, + particle_data->get_particle_mass(pdg_code), + particle_data->pdg_to_name(pdg_code), + four_momentum + ); + sg->particle_info(pinfo); + flag_update = true; + } + } + else if (nshowers[1]+1 == nvtx2_segs && nshowers[1] >= 2 && pdg == 2212) { + WireCell::Vector v1 = segment_cal_dir_3vector(sg, vtx2_pt, 5*units::cm); + double min_angle = 180; + + for (auto e_it = edge_range2.first; e_it != edge_range2.second; ++e_it) { + SegmentPtr sg2 = graph[*e_it].segment; + if (!sg2 || sg2 == sg) continue; + WireCell::Vector v2 = segment_cal_dir_3vector(sg2, vtx2_pt, 5*units::cm); + double angle = std::abs(v1.angle(v2) / 3.14159265 * 180.0 - 180.0); + if (angle < min_angle) min_angle = angle; + } + + double dQ_dx_rms = segment_rms_dQ_dx(sg); + + if ((dQ_dx_rms > 1.0 * (43e3/units::cm) && min_angle < 40) || + (dQ_dx_rms > 0.75 * (43e3/units::cm) && min_angle < 30) || + (dQ_dx_rms > 0.4 * (43e3/units::cm) && min_angle < 15)) { + + const auto& wcpts = sg->wcpts(); + auto front_pt = wcpts.front().point; + auto back_pt = wcpts.back().point; + double dis_front = ray_length(Ray{vtx2_pt, front_pt}); + double dis_back = ray_length(Ray{vtx2_pt, back_pt}); + bool flag_start = (dis_front < dis_back); + + if (flag_start) + sg->dirsign(-1); + else + sg->dirsign(1); + + int pdg_code = 11; + auto four_momentum = WireCell::D4Vector(0, 0, 0, 0); + if (sg->has_particle_info() && sg->particle_info()->energy() > 0) { + four_momentum = segment_cal_4mom(sg, pdg_code, particle_data, recomb_model); + } + auto pinfo = std::make_shared( + pdg_code, + particle_data->get_particle_mass(pdg_code), + particle_data->pdg_to_name(pdg_code), + four_momentum + ); + sg->particle_info(pinfo); + flag_update = true; + } + } + // Case E: Muon with specific topology conditions + else if (std::abs(pdg) == 13 && + ((nprotons[0] >= 0 && nmuons[0] >= 1 && nshowers[1]+1 == nvtx2_segs && nshowers[1] >= 2) || + (nprotons[1] >= 0 && nmuons[1] >= 1 && nshowers[0]+1 == nvtx1_segs && nshowers[0] >= 2) || + (((nprotons[0] >= 0 && nmuons[0] >= 1 && nshowers[1]+1 == nvtx2_segs && nshowers[1] >= 1) || + (nprotons[1] >= 0 && nmuons[1] >= 1 && nshowers[0]+1 == nvtx1_segs && nshowers[0] >= 1)) && + (sg->dirsign() == 0 || sg->dir_weak())))) { + + double direct_length = segment_track_direct_length(sg); + + if ((direct_length < 34*units::cm && direct_length < 0.93 * length) || + (length < 5*units::cm && ((nprotons[0] + nshowers[0] == 0 && nshowers[1] >= 2) || + (nprotons[1] + nshowers[1] == 0 && nshowers[0] >= 2)))) { + + int pdg_code = 11; + auto four_momentum = WireCell::D4Vector(0, 0, 0, 0); + if (sg->has_particle_info() && sg->particle_info()->energy() > 0) { + four_momentum = segment_cal_4mom(sg, pdg_code, particle_data, recomb_model); + } + auto pinfo = std::make_shared( + pdg_code, + particle_data->get_particle_mass(pdg_code), + particle_data->pdg_to_name(pdg_code), + four_momentum + ); + sg->particle_info(pinfo); + flag_update = true; + } + // Case F: Check daughter showers + else if ((((nshowers[0]+nshowers[1] >= 2) && (nprotons[0]+nmuons[0]+nshowers[0] == 1 || nprotons[1]+nmuons[1]+nshowers[1] == 1)) || + ((nshowers[0]+nshowers[1] >= 1) && (nprotons[0]+nmuons[0]+nshowers[0] > 1 || nprotons[1]+nmuons[1]+nshowers[1] > 1))) && + length < 40*units::cm) { + + int num_s1 = 0, num_s2 = 0; + double length_s1 = 0, length_s2 = 0; + double max_angle1 = 0, max_angle2 = 0; + + WireCell::Vector dir1 = segment_cal_dir_3vector(sg, vtx1_pt, 15*units::cm); + for (auto e_it = edge_range1.first; e_it != edge_range1.second; ++e_it) { + SegmentPtr sg1 = graph[*e_it].segment; + if (!sg1 || sg1 == sg) continue; + + WireCell::Vector dir2 = segment_cal_dir_3vector(sg1, vtx1_pt, 15*units::cm); + bool is_shower1 = sg1->flags_any(SegmentFlags::kShowerTrajectory) || + sg1->flags_any(SegmentFlags::kShowerTopology); + if (is_shower1) { + double angle = dir1.angle(dir2) / 3.14159265 * 180.0; + if (max_angle1 < angle) max_angle1 = angle; + + auto pair_result = calculate_num_daughter_showers(graph, two_vertices.first, sg1); + num_s1 += pair_result.first; + length_s1 += pair_result.second; + } + } + + dir1 = segment_cal_dir_3vector(sg, vtx2_pt, 10*units::cm); + for (auto e_it = edge_range2.first; e_it != edge_range2.second; ++e_it) { + SegmentPtr sg1 = graph[*e_it].segment; + if (!sg1 || sg1 == sg) continue; + + WireCell::Vector dir2 = segment_cal_dir_3vector(sg1, vtx2_pt, 15*units::cm); + bool is_shower1 = sg1->flags_any(SegmentFlags::kShowerTrajectory) || + sg1->flags_any(SegmentFlags::kShowerTopology); + if (is_shower1) { + double angle = dir1.angle(dir2) / 3.14159265 * 180.0; + if (max_angle2 < angle) max_angle2 = angle; + + auto pair_result = calculate_num_daughter_showers(graph, two_vertices.second, sg1); + num_s2 += pair_result.first; + length_s2 += pair_result.second; + } + } + + if (((num_s1 >= 4 || (length_s1 > 50*units::cm && num_s1 >= 2)) && max_angle1 > 150) || + ((num_s2 >= 4 || length_s2 > 50*units::cm) && max_angle2 > 150) || + (length < 6*units::cm && ((num_s1 >= 4 && length_s1 > 20*units::cm) || + (num_s2 >= 4 && length_s2 > 20*units::cm)))) { + + int pdg_code = 11; + auto four_momentum = WireCell::D4Vector(0, 0, 0, 0); + if (sg->has_particle_info() && sg->particle_info()->energy() > 0) { + four_momentum = segment_cal_4mom(sg, pdg_code, particle_data, recomb_model); + } + auto pinfo = std::make_shared( + pdg_code, + particle_data->get_particle_mass(pdg_code), + particle_data->pdg_to_name(pdg_code), + four_momentum + ); + sg->particle_info(pinfo); + flag_update = true; + } + } + } + // Case G: Muon with specific vertex connectivity + else if (std::abs(pdg) == 13 && (sg->dirsign() == 0 || sg->dir_weak()) && + ((nmuons[0]+nprotons[0]+nshowers[0] == 1) || (nmuons[1]+nprotons[1]+nshowers[1] == 1)) && + (nshowers[0] + nshowers[1] > 0 || segment_median_dQ_dx(sg) < 1.3*43e3/units::cm)) { + + bool flag_change = false; + + if (nvtx1_segs == 2) { + SegmentPtr tmp_sg = nullptr; + for (auto e_it = edge_range1.first; e_it != edge_range1.second; ++e_it) { + SegmentPtr candidate = graph[*e_it].segment; + if (candidate && candidate != sg) { + tmp_sg = candidate; + break; + } + } + if (tmp_sg) { + int tmp_pdg = tmp_sg->has_particle_info() ? tmp_sg->particle_info()->pdg() : 0; + if (tmp_pdg == 13 && segment_track_length(tmp_sg) > 4*length && length < 8*units::cm) { + flag_change = true; + } + } + } else if (nvtx2_segs == 2) { + SegmentPtr tmp_sg = nullptr; + for (auto e_it = edge_range2.first; e_it != edge_range2.second; ++e_it) { + SegmentPtr candidate = graph[*e_it].segment; + if (candidate && candidate != sg) { + tmp_sg = candidate; + break; + } + } + if (tmp_sg) { + int tmp_pdg = tmp_sg->has_particle_info() ? tmp_sg->particle_info()->pdg() : 0; + if (tmp_pdg == 13 && segment_track_length(tmp_sg) > 4*length && length < 8*units::cm) { + flag_change = true; + } + } + } + + if (flag_change) { + int pdg_code = 11; + auto four_momentum = WireCell::D4Vector(0, 0, 0, 0); + if (sg->has_particle_info() && sg->particle_info()->energy() > 0) { + four_momentum = segment_cal_4mom(sg, pdg_code, particle_data, recomb_model); + } + auto pinfo = std::make_shared( + pdg_code, + particle_data->get_particle_mass(pdg_code), + particle_data->pdg_to_name(pdg_code), + four_momentum + ); + sg->particle_info(pinfo); + flag_update = true; + } + } + + // Case B: Setting direction for segments between shower vertices + if (((nshowers[0]+1 == nvtx1_segs) || nshowers[0] > 0) && + ((nshowers[1]+1 == nvtx2_segs) || nshowers[1] > 0) && + (nshowers[0] + nshowers[1] > 2) && + ((nshowers[0]+1 == nvtx1_segs && nshowers[0] > 0) || + (nshowers[1]+1 == nvtx2_segs && nshowers[1] > 0))) { + + if ((length < 25*units::cm && pdg != 11) || sg->dirsign() == 0) { + const auto& wcpts = sg->wcpts(); + auto front_pt = wcpts.front().point; + auto back_pt = wcpts.back().point; + double dis_front = ray_length(Ray{vtx1_pt, front_pt}); + double dis_back = ray_length(Ray{vtx1_pt, back_pt}); + bool flag_start = (dis_front < dis_back); + + if (flag_start) { + if (nshowers[1] == 0) { + sg->dirsign(-1); + } else if (nshowers[0] == 0) { + sg->dirsign(1); + } + } else { + if (nshowers[1] == 0) { + sg->dirsign(1); + } else if (nshowers[0] == 0) { + sg->dirsign(-1); + } + } + sg->dir_weak(true); + + int pdg_code = 11; + auto four_momentum = WireCell::D4Vector(0, 0, 0, 0); + if (sg->has_particle_info() && sg->particle_info()->energy() > 0) { + four_momentum = segment_cal_4mom(sg, pdg_code, particle_data, recomb_model); + } + auto pinfo = std::make_shared( + pdg_code, + particle_data->get_particle_mass(pdg_code), + particle_data->pdg_to_name(pdg_code), + four_momentum + ); + sg->particle_info(pinfo); + flag_update = true; + } + } + // Case H: No particle type, short length, high dQ/dx, has showers + else if (pdg == 0 && length < 12*units::cm && + (nshowers[0] + nshowers[1] > 0) && + segment_median_dQ_dx(sg)/(43e3/units::cm) > 1.2) { + + bool flag_change = false; + + auto pair_result1 = calculate_num_daughter_showers(graph, two_vertices.second, sg); + auto pair_result2 = calculate_num_daughter_showers(graph, two_vertices.first, sg); + + if (pair_result1.first > 2) { + WireCell::Vector v1 = segment_cal_dir_3vector(sg, vtx1_pt, 10*units::cm); + double min_angle = 180; + double para_angle = 90; + + for (auto e_it = edge_range1.first; e_it != edge_range1.second; ++e_it) { + SegmentPtr sg2 = graph[*e_it].segment; + if (!sg2 || sg2 == sg) continue; + bool is_shower2 = sg2->flags_any(SegmentFlags::kShowerTrajectory) || + sg2->flags_any(SegmentFlags::kShowerTopology); + if (!is_shower2) continue; + + WireCell::Vector v2 = segment_cal_dir_3vector(sg2, vtx1_pt, 10*units::cm); + double angle = std::abs(v1.angle(v2) / 3.14159265 * 180.0 - 180.0); + if (angle < min_angle) { + min_angle = angle; + para_angle = std::abs(v2.angle(drift_dir) / 3.14159265 * 180.0 - 90); + } + } + + if (min_angle < 25 || + (std::abs(v1.angle(drift_dir) / 3.14159265 * 180.0 - 90) < 10 && + para_angle < 30 && min_angle < 45)) { + flag_change = true; + } + } + + if (!flag_change && pair_result2.first > 2) { + WireCell::Vector v1 = segment_cal_dir_3vector(sg, vtx2_pt, 10*units::cm); + double min_angle = 180; + double para_angle = 90; + + for (auto e_it = edge_range2.first; e_it != edge_range2.second; ++e_it) { + SegmentPtr sg2 = graph[*e_it].segment; + if (!sg2 || sg2 == sg) continue; + bool is_shower2 = sg2->flags_any(SegmentFlags::kShowerTrajectory) || + sg2->flags_any(SegmentFlags::kShowerTopology); + if (!is_shower2) continue; + + WireCell::Vector v2 = segment_cal_dir_3vector(sg2, vtx2_pt, 10*units::cm); + double angle = std::abs(v1.angle(v2) / 3.14159265 * 180.0 - 180.0); + if (angle < min_angle) { + min_angle = angle; + para_angle = std::abs(v2.angle(drift_dir) / 3.14159265 * 180.0 - 90); + } + } + + if (min_angle < 25 || + (std::abs(v1.angle(drift_dir) / 3.14159265 * 180.0 - 90) < 10 && + para_angle < 10 && min_angle < 45)) { + flag_change = true; + } + } + + if (flag_change) { + int pdg_code = 11; + auto four_momentum = WireCell::D4Vector(0, 0, 0, 0); + if (sg->has_particle_info() && sg->particle_info()->energy() > 0) { + four_momentum = segment_cal_4mom(sg, pdg_code, particle_data, recomb_model); + } + auto pinfo = std::make_shared( + pdg_code, + particle_data->get_particle_mass(pdg_code), + particle_data->pdg_to_name(pdg_code), + four_momentum + ); + sg->particle_info(pinfo); + flag_update = true; + } + } + + } // end if no direction or weak or proton + } // loop over all segments + } // while flag_update +} + +void PatternAlgorithms::improve_maps_multiple_tracks_in(Graph& graph, Facade::Cluster& cluster, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model){ + bool flag_update = true; + std::set used_vertices; + std::set used_segments; + + while(flag_update) { + flag_update = false; + + // Iterate through all vertices in the graph + auto [vbegin, vend] = boost::vertices(graph); + for (auto vit = vbegin; vit != vend; ++vit) { + VertexPtr vtx = graph[*vit].vertex; + + // Skip if vertex is null or doesn't belong to this cluster + if (!vtx || !vtx->cluster() || vtx->cluster() != &cluster) continue; + + // Skip if vertex has only 1 segment + if (!vtx->descriptor_valid()) continue; + auto vd = vtx->get_descriptor(); + if (boost::degree(vd, graph) <= 1) continue; + + // Skip if already processed + if (used_vertices.find(vtx) != used_vertices.end()) continue; + + int n_in = 0; + int n_in_shower = 0; + std::vector in_tracks; + + // Get vertex position + WireCell::Point vtx_point = vtx->wcpt().point; + + // Iterate through all segments connected to this vertex + auto edge_range = boost::out_edges(vd, graph); + for (auto eit = edge_range.first; eit != edge_range.second; ++eit) { + SegmentPtr sg = graph[*eit].segment; + if (!sg) continue; + + // Determine if this vertex is at the front or back of the segment + const auto& wcpts = sg->wcpts(); + if (wcpts.size() < 2) continue; + + auto front_pt = wcpts.front().point; + auto back_pt = wcpts.back().point; + + double dis_front = ray_length(Ray{vtx_point, front_pt}); + double dis_back = ray_length(Ray{vtx_point, back_pt}); + + bool flag_start = (dis_front < dis_back); // vertex is at the front of segment + + // Check if this is an "incoming" segment (pointing into vertex) + if ((flag_start && sg->dirsign() == -1) || (!flag_start && sg->dirsign() == 1)) { + n_in++; + + bool is_shower = sg->flags_any(SegmentFlags::kShowerTrajectory) || + sg->flags_any(SegmentFlags::kShowerTopology); + if (is_shower) { + n_in_shower++; + } else { + in_tracks.push_back(sg); + } + } + } + + // If there are multiple incoming segments and not all are showers + if (n_in > 1 && n_in != n_in_shower) { + // Reclassify all incoming tracks as electrons + for (auto it1 = in_tracks.begin(); it1 != in_tracks.end(); it1++) { + SegmentPtr sg1 = *it1; + + int pdg_code = 11; + auto four_momentum = WireCell::D4Vector(0, 0, 0, 0); + + // Recalculate 4-momentum if segment has valid energy + if (sg1->has_particle_info() && sg1->particle_info()->energy() > 0) { + four_momentum = segment_cal_4mom(sg1, pdg_code, particle_data, recomb_model); + } + + auto pinfo = std::make_shared( + pdg_code, + particle_data->get_particle_mass(pdg_code), + particle_data->pdg_to_name(pdg_code), + four_momentum + ); + sg1->particle_info(pinfo); + flag_update = true; + } + } + + used_vertices.insert(vtx); + } // loop over all vertices + } // while flag_update +} + +void PatternAlgorithms::judge_no_dir_tracks_close_to_showers(Graph& graph, Facade::Cluster& cluster, const Clus::ParticleDataSet::pointer& particle_data, IDetectorVolumes::pointer dv){ + std::set shower_set; + std::set no_dir_track_set; + + // Collect shower segments and no-direction track segments + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr sg = graph[*eit].segment; + if (!sg || sg->cluster() != &cluster) continue; + + bool is_shower = sg->flags_any(SegmentFlags::kShowerTrajectory) || + sg->flags_any(SegmentFlags::kShowerTopology); + + if (is_shower) { + shower_set.insert(sg); + } else { + if (sg->dirsign() == 0) { + no_dir_track_set.insert(sg); + } + } + } + + // Process each no-direction track segment + for (auto it = no_dir_track_set.begin(); it != no_dir_track_set.end(); it++) { + SegmentPtr sg = *it; + bool flag_change = true; + + const auto& pts = sg->wcpts(); + + // Check each point in the segment + for (size_t i = 0; i < pts.size(); i++) { + WireCell::Point test_p = pts.at(i).point; + + // Get apa and face for this point + auto test_wpid = dv->contained_by(test_p); + if (test_wpid.apa() == -1 || test_wpid.face() == -1) { + flag_change = false; + break; + } + + int apa = test_wpid.apa(); + int face = test_wpid.face(); + + double min_u_dis = 1e9; + double min_v_dis = 1e9; + double min_w_dis = 1e9; + + // Find minimum 2D distances to all shower segments + for (auto it1 = shower_set.begin(); it1 != shower_set.end(); it1++) { + auto [dist_u, dist_v, dist_w] = segment_get_closest_2d_distances(*it1, test_p, apa, face, "fit"); + + if (dist_u < min_u_dis) min_u_dis = dist_u; + if (dist_v < min_v_dis) min_v_dis = dist_v; + if (dist_w < min_w_dis) min_w_dis = dist_w; + } + + // If any distance exceeds threshold, don't reclassify + if (min_u_dis > 0.6*units::cm || min_v_dis > 0.6*units::cm || min_w_dis > 0.6*units::cm) { + flag_change = false; + break; + } + } + + // Reclassify segment as electron if all points are close to showers + if (flag_change) { + int pdg_code = 11; + auto pinfo = std::make_shared( + pdg_code, + particle_data->get_particle_mass(pdg_code), + particle_data->pdg_to_name(pdg_code), + WireCell::D4Vector(0, 0, 0, 0) + ); + sg->particle_info(pinfo); + } + } +} + +bool PatternAlgorithms::examine_maps(Graph&graph, Facade::Cluster& cluster){ + bool flag_return = true; + + // Iterate through all vertices in the graph + auto [vbegin, vend] = boost::vertices(graph); + for (auto vit = vbegin; vit != vend; ++vit) { + VertexPtr vtx = graph[*vit].vertex; + + // Skip if vertex is null or doesn't belong to this cluster + if (!vtx || vtx->cluster() != &cluster) continue; + + // Skip vertices with only 1 segment + if (!vtx->descriptor_valid()) continue; + auto vd = vtx->get_descriptor(); + if (boost::degree(vd, graph) <= 1) continue; + + int n_in = 0; + int n_in_shower = 0; + int n_out_tracks = 0; + + // Get vertex position + WireCell::Point vtx_point = vtx->wcpt().point; + + // Iterate through all segments connected to this vertex + auto edge_range = boost::out_edges(vd, graph); + for (auto eit = edge_range.first; eit != edge_range.second; ++eit) { + SegmentPtr sg = graph[*eit].segment; + if (!sg) continue; + + // Determine if this vertex is at the front or back of the segment + const auto& wcpts = sg->wcpts(); + if (wcpts.size() < 2) continue; + + auto front_pt = wcpts.front().point; + auto back_pt = wcpts.back().point; + + double dis_front = ray_length(Ray{vtx_point, front_pt}); + double dis_back = ray_length(Ray{vtx_point, back_pt}); + + bool flag_start = (dis_front < dis_back); // vertex is at the front of segment + + bool is_shower = sg->flags_any(SegmentFlags::kShowerTrajectory) || + sg->flags_any(SegmentFlags::kShowerTopology); + + // Check if this is an "incoming" segment (pointing into vertex) + if ((flag_start && sg->dirsign() == -1) || (!flag_start && sg->dirsign() == 1)) { + n_in++; + if (is_shower) { + n_in_shower++; + } + } + + // Check if this is an "outgoing" track (pointing away from vertex) + if ((flag_start && sg->dirsign() == 1) || (!flag_start && sg->dirsign() == -1)) { + if (!is_shower) { + n_out_tracks++; + } + } + } + + // Check for violations + if (n_in > 1 && n_in != n_in_shower) { + std::cout << "Wrong: Multiple (" << n_in << ") particles into a vertex!" << std::endl; + print_segs_info(graph, cluster, vtx); + flag_return = false; + } + + if (n_in_shower > 0 && n_out_tracks > 0) { + std::cout << "Wrong: " << n_in_shower << " showers in and " << n_out_tracks << " tracks out!" << std::endl; + print_segs_info(graph, cluster, vtx); + flag_return = false; + } + } + + return flag_return; +} From 717489fde49ac172f4434f4cb948eb922bce7886 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Thu, 18 Dec 2025 15:48:31 -0800 Subject: [PATCH 073/111] implement the examine_all_showers --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 1 + clus/inc/WireCellClus/PRSegment.h | 3 + clus/src/NeutrinoTrackShowerSep.cxx | 318 ++++++++++++++++++++ clus/src/PRSegmentFunctions.cxx | 10 +- 4 files changed, 328 insertions(+), 4 deletions(-) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 300d311c..759190c9 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -94,6 +94,7 @@ namespace WireCell::Clus::PR { void improve_maps_multiple_tracks_in(Graph& graph, Facade::Cluster& cluster, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); void judge_no_dir_tracks_close_to_showers(Graph& graph, Facade::Cluster& cluster, const Clus::ParticleDataSet::pointer& particle_data, IDetectorVolumes::pointer dv); bool examine_maps(Graph&graph, Facade::Cluster& cluster); + void examine_all_showers(Graph& graph, Facade::Cluster& cluster, const Clus::ParticleDataSet::pointer& particle_data); // vertex related functions bool search_for_vertex_activities(Graph& graph, VertexPtr vertex, std::set& segments_set, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, double search_range = 1.5*units::cm); diff --git a/clus/inc/WireCellClus/PRSegment.h b/clus/inc/WireCellClus/PRSegment.h index b9b19430..d28990ba 100644 --- a/clus/inc/WireCellClus/PRSegment.h +++ b/clus/inc/WireCellClus/PRSegment.h @@ -67,6 +67,8 @@ namespace WireCell::Clus::PR { // Getters const std::shared_ptr& particle_info() const { return m_particle_info; } std::shared_ptr& particle_info() { return m_particle_info; } + double particle_score() const { return m_particle_score; } + void particle_score(double score) { m_particle_score = score; } // Chainable setter Segment& particle_info(std::shared_ptr pinfo) { @@ -147,6 +149,7 @@ namespace WireCell::Clus::PR { bool m_dir_weak{false}; std::shared_ptr m_particle_info{nullptr}; + double m_particle_score{100}; // Mapping from local DPC index to global cluster point index std::map> m_pc_global_indices; diff --git a/clus/src/NeutrinoTrackShowerSep.cxx b/clus/src/NeutrinoTrackShowerSep.cxx index 77b99e5d..aa0e0ece 100644 --- a/clus/src/NeutrinoTrackShowerSep.cxx +++ b/clus/src/NeutrinoTrackShowerSep.cxx @@ -1377,3 +1377,321 @@ bool PatternAlgorithms::examine_maps(Graph&graph, Facade::Cluster& cluster){ return flag_return; } + +void PatternAlgorithms::examine_all_showers(Graph& graph, Facade::Cluster& cluster, const Clus::ParticleDataSet::pointer& particle_data){ + int n_good_tracks = 0, n_tracks = 0, n_showers = 0; + double length_good_tracks = 0, length_tracks = 0, length_showers = 0; + double tracks_score = 0; + SegmentPtr good_track = nullptr; + + double maximal_length = 0; + SegmentPtr maximal_length_track = nullptr; + + // Count segments and their properties + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr sg = graph[*eit].segment; + if (!sg || sg->cluster() != &cluster) continue; + + double length = segment_track_length(sg); + bool is_shower = sg->flags_any(SegmentFlags::kShowerTrajectory) || + sg->flags_any(SegmentFlags::kShowerTopology); + + if (is_shower) { + n_showers++; + length_showers += length; + } else { + if (sg->dirsign() != 0 && !sg->dir_weak()) { + good_track = sg; + n_good_tracks++; + length_good_tracks += length; + } else { + n_tracks++; + length_tracks += length; + if (length > maximal_length) { + maximal_length = length; + maximal_length_track = sg; + } + if (sg->particle_score() != 100) tracks_score += sg->particle_score(); + } + } + } + + if (n_good_tracks + n_tracks + n_showers == 1) return; + + // If there is only one good track + if (n_good_tracks == 1 && (length_good_tracks < 0.15 * (length_showers + length_tracks)) && length_good_tracks < 10*units::cm) { + auto pair_vertices = find_vertices(graph, good_track); + + int num_s1 = 0, num_s2 = 0; + double length_s1 = 0, length_s2 = 0; + + auto pair_result1 = calculate_num_daughter_showers(graph, pair_vertices.first, good_track); + auto pair_result2 = calculate_num_daughter_showers(graph, pair_vertices.second, good_track); + num_s1 = pair_result1.first; + length_s1 = pair_result1.second; + num_s2 = pair_result2.first; + length_s2 = pair_result2.second; + + if (num_s1 > 0 && length_s1 > length_good_tracks) { + double max_angle = 0; + WireCell::Point vtx2_pt = pair_vertices.second->fit().valid() ? pair_vertices.second->fit().point : pair_vertices.second->wcpt().point; + WireCell::Vector dir1 = segment_cal_dir_3vector(good_track, vtx2_pt, 15*units::cm); + + if (pair_vertices.second->descriptor_valid()) { + auto vd2 = pair_vertices.second->get_descriptor(); + auto edge_range = boost::out_edges(vd2, graph); + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr sg1 = graph[*e_it].segment; + if (!sg1 || sg1 == good_track) continue; + + WireCell::Vector dir2 = segment_cal_dir_3vector(sg1, vtx2_pt, 15*units::cm); + double angle = dir1.angle(dir2) / 3.14159265 * 180.0; + if (max_angle < angle) max_angle = angle; + } + } + + if (max_angle > 165 || (max_angle > 150 && length_good_tracks < 3.0*units::cm && length_good_tracks < 0.1 * length_showers)) { + n_good_tracks = 0; + length_tracks += length_good_tracks; + } + } + + if (num_s2 > 0 && length_s2 > length_good_tracks && n_good_tracks > 0) { + double max_angle = 0; + WireCell::Point vtx1_pt = pair_vertices.first->fit().valid() ? pair_vertices.first->fit().point : pair_vertices.first->wcpt().point; + WireCell::Vector dir1 = segment_cal_dir_3vector(good_track, vtx1_pt, 15*units::cm); + + if (pair_vertices.first->descriptor_valid()) { + auto vd1 = pair_vertices.first->get_descriptor(); + auto edge_range = boost::out_edges(vd1, graph); + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr sg1 = graph[*e_it].segment; + if (!sg1 || sg1 == good_track) continue; + + WireCell::Vector dir2 = segment_cal_dir_3vector(sg1, vtx1_pt, 15*units::cm); + double angle = dir1.angle(dir2) / 3.14159265 * 180.0; + if (max_angle < angle) max_angle = angle; + } + } + + if (max_angle > 165) { + n_good_tracks = 0; + length_tracks += length_good_tracks; + } + } + + // Check vertex connectivity and beam angle + if (pair_vertices.first && pair_vertices.second) { + int nvtx1_segs = 0, nvtx2_segs = 0; + if (pair_vertices.first->descriptor_valid()) { + nvtx1_segs = boost::degree(pair_vertices.first->get_descriptor(), graph); + } + if (pair_vertices.second->descriptor_valid()) { + nvtx2_segs = boost::degree(pair_vertices.second->get_descriptor(), graph); + } + + if (nvtx1_segs == 1 && nvtx2_segs > 1) { + double max_length = 0; + SegmentPtr max_segment = nullptr; + + if (pair_vertices.second->descriptor_valid()) { + auto vd2 = pair_vertices.second->get_descriptor(); + auto edge_range = boost::out_edges(vd2, graph); + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr sg = graph[*e_it].segment; + if (!sg) continue; + + bool is_shower = sg->flags_any(SegmentFlags::kShowerTrajectory) || + sg->flags_any(SegmentFlags::kShowerTopology); + if (is_shower) { + double length = segment_track_length(sg); + if (length > max_length) { + max_length = length; + max_segment = sg; + } + } + } + } + + if (max_segment != nullptr && max_length > 5*units::cm) { + WireCell::Point vtx2_pt = pair_vertices.second->fit().valid() ? pair_vertices.second->fit().point : pair_vertices.second->wcpt().point; + WireCell::Vector dir = segment_cal_dir_3vector(max_segment, vtx2_pt, 15*units::cm); + WireCell::Vector beam_dir(0, 0, 1); + if (beam_dir.angle(dir) / 3.14159265 * 180.0 > 90) { + n_good_tracks = 0; + length_tracks += length_good_tracks; + } + } + } else if (nvtx1_segs > 1 && nvtx2_segs == 1) { + double max_length = 0; + SegmentPtr max_segment = nullptr; + + if (pair_vertices.first->descriptor_valid()) { + auto vd1 = pair_vertices.first->get_descriptor(); + auto edge_range = boost::out_edges(vd1, graph); + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr sg = graph[*e_it].segment; + if (!sg) continue; + + bool is_shower = sg->flags_any(SegmentFlags::kShowerTrajectory) || + sg->flags_any(SegmentFlags::kShowerTopology); + if (is_shower) { + double length = segment_track_length(sg); + if (length > max_length) { + max_length = length; + max_segment = sg; + } + } + } + } + + if (max_segment != nullptr && max_length > 5*units::cm) { + WireCell::Point vtx1_pt = pair_vertices.first->fit().valid() ? pair_vertices.first->fit().point : pair_vertices.first->wcpt().point; + WireCell::Vector dir = segment_cal_dir_3vector(max_segment, vtx1_pt, 15*units::cm); + WireCell::Vector beam_dir(0, 0, 1); + if (beam_dir.angle(dir) / 3.14159265 * 180.0 > 90) { + n_good_tracks = 0; + length_tracks += length_good_tracks; + } + } + } + } + } else if (n_good_tracks == 0 && (n_tracks == 2 && length_tracks <= 35*units::cm)) { + if (maximal_length_track != nullptr) { + auto pair_vertices = find_vertices(graph, maximal_length_track); + + if (pair_vertices.first && pair_vertices.second) { + int nvtx1_segs = 0, nvtx2_segs = 0; + if (pair_vertices.first->descriptor_valid()) { + nvtx1_segs = boost::degree(pair_vertices.first->get_descriptor(), graph); + } + if (pair_vertices.second->descriptor_valid()) { + nvtx2_segs = boost::degree(pair_vertices.second->get_descriptor(), graph); + } + + if (nvtx1_segs < nvtx2_segs) { + WireCell::Point vtx2_pt = pair_vertices.second->fit().valid() ? pair_vertices.second->fit().point : pair_vertices.second->wcpt().point; + WireCell::Vector dir = segment_cal_dir_3vector(maximal_length_track, vtx2_pt, 15*units::cm); + WireCell::Vector beam_dir(0, 0, 1); + if (beam_dir.angle(dir) / 3.14159265 * 180.0 > 100) { + n_tracks--; + length_tracks -= maximal_length; + n_showers++; + length_showers += maximal_length; + } + } else if (nvtx1_segs > nvtx2_segs) { + WireCell::Point vtx1_pt = pair_vertices.first->fit().valid() ? pair_vertices.first->fit().point : pair_vertices.first->wcpt().point; + WireCell::Vector dir = segment_cal_dir_3vector(maximal_length_track, vtx1_pt, 15*units::cm); + WireCell::Vector beam_dir(0, 0, 1); + if (beam_dir.angle(dir) / 3.14159265 * 180.0 > 90) { + n_tracks--; + length_tracks -= maximal_length; + n_showers++; + length_showers += maximal_length; + } + } + } + } + } + + bool flag_change_showers = false; + + // Check main_cluster status + bool is_main_cluster = cluster.get_flag(Facade::Flags::main_cluster); + + if (n_good_tracks == 0) { + if (length_tracks < 1.0/3.0 * length_showers || (length_tracks < 2.0/3.0 * length_showers && n_tracks == 1)) { + if ((length_showers + length_tracks) < 40*units::cm) { + flag_change_showers = true; + } else if (length_tracks < 0.18 * length_showers && ((length_showers + length_tracks) < 60*units::cm || length_tracks < 12*units::cm)) { + flag_change_showers = true; + } else if (length_tracks < 0.25 * length_showers && ((tracks_score == 0 && length_tracks < 30*units::cm) || length_tracks < 10*units::cm)) { + flag_change_showers = true; + } else if (n_tracks == 1 && tracks_score == 0 && length_tracks < 15*units::cm && length_tracks < 1.0/3.0 * length_showers) { + flag_change_showers = true; + } + } else if ((length_tracks < 35*units::cm && length_tracks + length_showers < 50*units::cm && length_showers < 15*units::cm) && + (!is_main_cluster || + (is_main_cluster && + (length_showers > 0.5*length_tracks || + (length_showers > 0.3*length_tracks && n_showers >= 2) || + (n_showers == 1 && n_tracks == 1 && length_showers > length_tracks * 0.3) || + tracks_score == 0)))) { + flag_change_showers = true; + if (length_showers == 0 && n_tracks <= 2 && (is_main_cluster || length_tracks > 15*units::cm)) { + flag_change_showers = false; + } + } else if (length_tracks < 35*units::cm && length_tracks + length_showers < 50*units::cm && length_showers < 15*units::cm) { + + // Check if all non-shower segments are connected to shower segments + if (flag_change_showers) { + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr sg = graph[*eit].segment; + if (!sg || sg->cluster() != &cluster) continue; + + bool is_shower = sg->flags_any(SegmentFlags::kShowerTrajectory) || + sg->flags_any(SegmentFlags::kShowerTopology); + + if (!is_shower) { + auto pair_vertices = find_vertices(graph, sg); + bool flag_shower = false; + + if (pair_vertices.first && pair_vertices.first->descriptor_valid()) { + auto vd1 = pair_vertices.first->get_descriptor(); + auto edge_range = boost::out_edges(vd1, graph); + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr sg1 = graph[*e_it].segment; + if (sg1 && (sg1->flags_any(SegmentFlags::kShowerTrajectory) || + sg1->flags_any(SegmentFlags::kShowerTopology))) { + flag_shower = true; + break; + } + } + } + + if (!flag_shower && pair_vertices.second && pair_vertices.second->descriptor_valid()) { + auto vd2 = pair_vertices.second->get_descriptor(); + auto edge_range = boost::out_edges(vd2, graph); + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr sg1 = graph[*e_it].segment; + if (sg1 && (sg1->flags_any(SegmentFlags::kShowerTrajectory) || + sg1->flags_any(SegmentFlags::kShowerTopology))) { + flag_shower = true; + break; + } + } + } + + if (!flag_shower) { + flag_change_showers = false; + break; + } + } + } + } + } + } + + if (flag_change_showers) { + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr sg = graph[*eit].segment; + if (!sg || sg->cluster() != &cluster) continue; + + bool is_shower = sg->flags_any(SegmentFlags::kShowerTrajectory) || + sg->flags_any(SegmentFlags::kShowerTopology); + + if (!is_shower) { + int pdg_code = 11; + double electron_mass = particle_data->get_particle_mass(pdg_code); + auto pinfo = std::make_shared( + pdg_code, + electron_mass, + particle_data->pdg_to_name(pdg_code), + WireCell::D4Vector(0, 0, 0, 0) + ); + sg->particle_info(pinfo); + } + } + } +} diff --git a/clus/src/PRSegmentFunctions.cxx b/clus/src/PRSegmentFunctions.cxx index a531b360..74891c6b 100644 --- a/clus/src/PRSegmentFunctions.cxx +++ b/clus/src/PRSegmentFunctions.cxx @@ -1291,7 +1291,7 @@ namespace WireCell::Clus::PR { // Decision logic int flag_dir = 0; int particle_type = 0; - double particle_score = 0.0; + double particle_score = 100.0; if (flag_forward == 1 && flag_backward == 0) { flag_dir = 1; @@ -1335,7 +1335,7 @@ namespace WireCell::Clus::PR { segment->dirsign(flag_dir); // Reset before return - failure case - return std::make_tuple(false, 0, 0, 0.0); + return std::make_tuple(false, 0, 0, 100.0); } // 4-momentum: E, px, py, pz, @@ -1406,7 +1406,7 @@ namespace WireCell::Clus::PR { } int pdg_code = 0; - double particle_score = 0.0; + double particle_score = 100.0; if (npoints >= 2) { // reasonably long bool tmp_flag_pid = false; @@ -1524,6 +1524,7 @@ namespace WireCell::Clus::PR { // Store particle info in segment segment->particle_info(pinfo); + segment->particle_score(particle_score); } if (flag_print) { @@ -1552,7 +1553,7 @@ namespace WireCell::Clus::PR { // hack for now ... int pdg_code = 11; // electron - double particle_score = 0.0; + double particle_score = 100.0; if (start_n == 1 && end_n > 1){ segment->dirsign(-1); @@ -1590,6 +1591,7 @@ namespace WireCell::Clus::PR { // Store particle info in segment segment->particle_info(pinfo); + segment->particle_score(particle_score); if (flag_print) { // Match WCPPID output format: id, length, "S_traj", flag_dir, is_dir_weak, particle_type, mass, KE, particle_score From b928b5c655ed8062039ae9c72490fbfd186427bc Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Thu, 18 Dec 2025 16:03:01 -0800 Subject: [PATCH 074/111] implement EM shower related --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 1 + clus/src/NeutrinoTrackShowerSep.cxx | 38 +++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 759190c9..b1f51bcd 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -95,6 +95,7 @@ namespace WireCell::Clus::PR { void judge_no_dir_tracks_close_to_showers(Graph& graph, Facade::Cluster& cluster, const Clus::ParticleDataSet::pointer& particle_data, IDetectorVolumes::pointer dv); bool examine_maps(Graph&graph, Facade::Cluster& cluster); void examine_all_showers(Graph& graph, Facade::Cluster& cluster, const Clus::ParticleDataSet::pointer& particle_data); + void shower_determining_in_main_cluster(Graph& graph, Facade::Cluster& cluster, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model, IDetectorVolumes::pointer dv); // vertex related functions bool search_for_vertex_activities(Graph& graph, VertexPtr vertex, std::set& segments_set, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, double search_range = 1.5*units::cm); diff --git a/clus/src/NeutrinoTrackShowerSep.cxx b/clus/src/NeutrinoTrackShowerSep.cxx index aa0e0ece..cecb237d 100644 --- a/clus/src/NeutrinoTrackShowerSep.cxx +++ b/clus/src/NeutrinoTrackShowerSep.cxx @@ -1695,3 +1695,41 @@ void PatternAlgorithms::examine_all_showers(Graph& graph, Facade::Cluster& clust } } } + +void PatternAlgorithms::shower_determining_in_main_cluster(Graph& graph, Facade::Cluster& cluster, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model, IDetectorVolumes::pointer dv){ + // Examine good tracks first + examine_good_tracks(graph, cluster, particle_data); + + // If multiple tracks in, make them undetermined + fix_maps_multiple_tracks_in(graph, cluster); + + // If one shower in and a good track out, reverse the shower + fix_maps_shower_in_track_out(graph, cluster); + + // If there is one good track in, turn everything else to out + improve_maps_one_in(graph, cluster, particle_data, recomb_model); + + // If one shower in and a track out, change the track to shower + improve_maps_shower_in_track_out(graph, cluster, particle_data, recomb_model); + + // Help to change tracks around shower to showers + improve_maps_no_dir_tracks(graph, cluster, particle_data, recomb_model); + + // If one shower in and a track out, change the track to shower (no reverse flag) + improve_maps_shower_in_track_out(graph, cluster, particle_data, recomb_model, false); + + // If multiple tracks in, change track to shower + improve_maps_multiple_tracks_in(graph, cluster, particle_data, recomb_model); + + // If one shower in and a good track out, reverse the shower + fix_maps_shower_in_track_out(graph, cluster); + + // Judgement for no-direction tracks close to showers + judge_no_dir_tracks_close_to_showers(graph, cluster, particle_data, dv); + + // Examine maps for physics violations + examine_maps(graph, cluster); + + // Examine all showers comprehensively + examine_all_showers(graph, cluster, particle_data); +} From e8e3ea3d9002010b23868edba3f327bb3efc1825 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Thu, 1 Jan 2026 16:56:21 -0800 Subject: [PATCH 075/111] implement more functions --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 7 + clus/src/NeutrinoOtherSegments.cxx | 30 ++- clus/src/NeutrinoPatternBase.cxx | 56 +++++ clus/src/NeutrinoVertexFinder.cxx | 259 ++++++++++++++++++++ 4 files changed, 351 insertions(+), 1 deletion(-) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index b1f51bcd..77741ab3 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -97,8 +97,13 @@ namespace WireCell::Clus::PR { void examine_all_showers(Graph& graph, Facade::Cluster& cluster, const Clus::ParticleDataSet::pointer& particle_data); void shower_determining_in_main_cluster(Graph& graph, Facade::Cluster& cluster, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model, IDetectorVolumes::pointer dv); + // PCA calculation + std::pair calc_PCA_main_axis(std::vector& points); + // vertex related functions bool search_for_vertex_activities(Graph& graph, VertexPtr vertex, std::set& segments_set, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, double search_range = 1.5*units::cm); + std::tuple examine_main_vertex_candidate(Graph& graph, VertexPtr vertex); + VertexPtr compare_main_vertices_all_showers(Graph& graph, Facade::Cluster& cluster, std::vector& vertex_candidates, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); // global information transfer void transfer_info_from_segment_to_cluster(Graph& graph, Facade::Cluster& cluster, const std::string& cloud_name = "associated_points"); @@ -106,5 +111,7 @@ namespace WireCell::Clus::PR { // print information void print_segs_info(Graph& graph, Facade::Cluster& cluster, VertexPtr vertex= nullptr); + + }; } diff --git a/clus/src/NeutrinoOtherSegments.cxx b/clus/src/NeutrinoOtherSegments.cxx index 89890029..b8ef136c 100644 --- a/clus/src/NeutrinoOtherSegments.cxx +++ b/clus/src/NeutrinoOtherSegments.cxx @@ -235,7 +235,35 @@ void PatternAlgorithms::find_other_segments(Graph& graph, Facade::Cluster& clust } if (!candidates_special_A.empty()) { - special_A = candidates_special_A.front(); + if (candidates_special_A.size() > 1 && ncounts[i] > 6) { + // Use PCA to find the best connection point + std::vector tmp_points; + for (int j = 0; j < ncounts[i]; j++) { + Facade::geo_point_t tmp_p(x_coords[sep_clusters[i][j]], + y_coords[sep_clusters[i][j]], + z_coords[sep_clusters[i][j]]); + tmp_points.push_back(tmp_p); + } + + auto results_pca = calc_PCA_main_axis(tmp_points); + double max_val = 0; + + for (size_t j = 0; j < candidates_special_A.size(); j++) { + size_t idx = candidates_special_A[j]; + double val = std::abs( + (x_coords[idx] - results_pca.first.x()) * results_pca.second.x() + + (y_coords[idx] - results_pca.first.y()) * results_pca.second.y() + + (z_coords[idx] - results_pca.first.z()) * results_pca.second.z() + ); + + if (val > max_val) { + max_val = val; + special_A = idx; + } + } + } else { + special_A = candidates_special_A.front(); + } } // Find furthest point (special_B) diff --git a/clus/src/NeutrinoPatternBase.cxx b/clus/src/NeutrinoPatternBase.cxx index 208dc832..88c65755 100644 --- a/clus/src/NeutrinoPatternBase.cxx +++ b/clus/src/NeutrinoPatternBase.cxx @@ -1,5 +1,6 @@ #include "WireCellClus/NeutrinoPatternBase.h" #include "WireCellClus/PRSegmentFunctions.h" +#include using namespace WireCell::Clus::PR; using namespace WireCell::Clus; @@ -1274,3 +1275,58 @@ void PatternAlgorithms::print_segs_info(Graph& graph, Facade::Cluster& cluster, } } } + + +std::pair PatternAlgorithms::calc_PCA_main_axis(std::vector& points){ + Facade::geo_point_t center(0, 0, 0); + int nsum = 0; + + // Calculate the center point + for (size_t i = 0; i < points.size(); i++) { + center += points[i]; + nsum++; + } + + Facade::geo_vector_t PCA_main_axis(0, 0, 0); + + // Need at least 3 points for PCA + if (nsum < 3) { + center.set(0, 0, 0); + return std::make_pair(center, PCA_main_axis); + } + + center /= nsum; + + // Build covariance matrix + Eigen::MatrixXd cov_matrix(3, 3); + + for (int i = 0; i != 3; i++) { + for (int j = i; j != 3; j++) { + cov_matrix(i, j) = 0; + for (size_t k = 0; k < points.size(); k++) { + cov_matrix(i, j) += (points[k][i] - center[i]) * (points[k][j] - center[j]); + } + } + } + + // Fill in symmetric part + cov_matrix(1, 0) = cov_matrix(0, 1); + cov_matrix(2, 0) = cov_matrix(0, 2); + cov_matrix(2, 1) = cov_matrix(1, 2); + + // Compute eigenvalues and eigenvectors + Eigen::SelfAdjointEigenSolver eigenSolver(cov_matrix); + auto eigen_vectors = eigenSolver.eigenvectors(); + + // Get the principal component (largest eigenvalue, which is index 2 in ascending order) + int i = 2; // Eigen returns eigenvalues in ascending order, so index 2 is the largest + double norm = sqrt(eigen_vectors(0, i) * eigen_vectors(0, i) + + eigen_vectors(1, i) * eigen_vectors(1, i) + + eigen_vectors(2, i) * eigen_vectors(2, i)); + + PCA_main_axis.set(eigen_vectors(0, i) / norm, + eigen_vectors(1, i) / norm, + eigen_vectors(2, i) / norm); + + return std::make_pair(center, PCA_main_axis); +} diff --git a/clus/src/NeutrinoVertexFinder.cxx b/clus/src/NeutrinoVertexFinder.cxx index 07b064b8..03bda4fb 100644 --- a/clus/src/NeutrinoVertexFinder.cxx +++ b/clus/src/NeutrinoVertexFinder.cxx @@ -208,3 +208,262 @@ bool WireCell::Clus::PR::PatternAlgorithms::search_for_vertex_activities(Graph& return false; } +std::tuple PatternAlgorithms::examine_main_vertex_candidate(Graph& graph, VertexPtr vertex){ + bool flag_in = false; + int ntracks = 0; + int nshowers = 0; + SegmentPtr shower_cand = nullptr; + SegmentPtr track_cand = nullptr; + + // Get all segments connected to this vertex + if (!vertex || !vertex->descriptor_valid()) { + return std::make_tuple(flag_in, ntracks, nshowers); + } + + auto vd = vertex->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + + for (auto eit = edge_range.first; eit != edge_range.second; ++eit) { + SegmentPtr sg = graph[*eit].segment; + if (!sg) continue; + + // Check if segment is a shower (has kShowerTrajectory or kShowerTopology flags) + bool is_shower = sg->flags_any(SegmentFlags::kShowerTrajectory) || + sg->flags_any(SegmentFlags::kShowerTopology); + + if (is_shower) { + nshowers++; + shower_cand = sg; + } else { + ntracks++; + track_cand = sg; + } + + // Determine which end of segment connects to vertex + const auto& wcps = sg->wcpts(); + if (wcps.empty()) continue; + + bool flag_start = (ray_length(Ray{wcps.front().point, vertex->wcpt().point}) < + ray_length(Ray{wcps.back().point, vertex->wcpt().point})); + + // Check if segment is pointing IN to the vertex (strong direction) + int dir_sign = sg->dirsign(); + bool is_dir_weak = sg->dir_weak(); + + if (flag_start && dir_sign == -1 && !is_dir_weak) { + flag_in = true; + break; + } else if (!flag_start && dir_sign == 1 && !is_dir_weak) { + flag_in = true; + break; + } + } + + // Check Michel electron case: 2 segments (1 track + 1 shower) + int num_segments = 0; + auto edge_range2 = boost::out_edges(vd, graph); + for (auto eit = edge_range2.first; eit != edge_range2.second; ++eit) { + if (graph[*eit].segment) num_segments++; + } + + if (num_segments == 2 && ntracks == 1 && nshowers == 1 && track_cand && shower_cand) { + // Calculate the number of daughter showers + auto pair_result = calculate_num_daughter_showers(graph, vertex, shower_cand); + + if (pair_result.first <= 3 && pair_result.second < 30 * units::cm) { + const auto& track_wcps = track_cand->wcpts(); + if (!track_wcps.empty()) { + bool flag_start = (ray_length(Ray{track_wcps.front().point, vertex->wcpt().point}) < + ray_length(Ray{track_wcps.back().point, vertex->wcpt().point})); + + int track_dir = track_cand->dirsign(); + if ((flag_start && track_dir == -1) || (!flag_start && track_dir == 1)) { + flag_in = true; + } + } + } + } + + return std::make_tuple(flag_in, ntracks, nshowers); +} + +VertexPtr PatternAlgorithms::compare_main_vertices_all_showers(Graph& graph, Facade::Cluster& cluster, std::vector& vertex_candidates, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model){ + if (vertex_candidates.empty()) return nullptr; + + VertexPtr temp_main_vertex = vertex_candidates.front(); + + // Collect all points from segments and vertices in the cluster + std::vector pts; + + // Collect points from segments + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr sg = graph[*eit].segment; + if (!sg || sg->cluster() != &cluster) continue; + + const auto& wcpts = sg->wcpts(); + if (wcpts.size() <= 2) continue; + + for (size_t i = 1; i + 1 < wcpts.size(); i++) { + pts.push_back(wcpts[i].point); + } + } + + // Collect points from vertices + auto [vbegin, vend] = boost::vertices(graph); + for (auto vit = vbegin; vit != vend; ++vit) { + VertexPtr vtx = graph[*vit].vertex; + if (!vtx || vtx->cluster() != &cluster) continue; + pts.push_back(vtx->wcpt().point); + } + + if (pts.size() <= 3) { + return temp_main_vertex; + } + + // Calculate PCA main axis + auto pair_result = calc_PCA_main_axis(pts); + Facade::geo_vector_t dir = pair_result.second; + Facade::geo_point_t center = pair_result.first; + + // Find min and max vertices along the main axis + double min_val = 1e9, max_val = -1e9; + VertexPtr min_vtx = nullptr, max_vtx = nullptr; + + for (auto vtx : vertex_candidates) { + double val = (vtx->wcpt().point.x() - center.x()) * dir.x() + + (vtx->wcpt().point.y() - center.y()) * dir.y() + + (vtx->wcpt().point.z() - center.z()) * dir.z(); + + // Adjust for single short segment vertices + if (vtx->descriptor_valid()) { + auto vd = vtx->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + int num_segs = 0; + double seg_length = 0; + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + if (graph[*e_it].segment) { + num_segs++; + seg_length = segment_track_length(graph[*e_it].segment); + } + } + + if (num_segs == 1 && seg_length < 1 * units::cm) { + if (val > 0) val -= 0.5 * units::cm; + else if (val < 0) val += 0.5 * units::cm; + } + } + + if (val > max_val) { + max_val = val; + max_vtx = vtx; + } + if (val < min_val) { + min_val = val; + min_vtx = vtx; + } + } + + if (!min_vtx || !max_vtx || min_vtx == max_vtx) { + return temp_main_vertex; + } + + // Check if steiner point cloud exists + const auto& steiner_pc = cluster.get_pc("steiner_pc"); + if (steiner_pc.size() < 3) { + // Pick forward vertex based on z coordinate + if (max_vtx->wcpt().point.z() < min_vtx->wcpt().point.z()) { + temp_main_vertex = max_vtx; + } else { + temp_main_vertex = min_vtx; + } + return temp_main_vertex; + } + + // Find path between min and max vertices using steiner graph + auto path_points = do_rough_path(cluster, max_vtx->wcpt().point, min_vtx->wcpt().point); + + if (path_points.size() <= 2) { + // Pick forward vertex based on z coordinate + if (max_vtx->wcpt().point.z() < min_vtx->wcpt().point.z()) { + temp_main_vertex = max_vtx; + } else { + temp_main_vertex = min_vtx; + } + return temp_main_vertex; + } + + // Create temporary local graph for fitting + auto local_graph = std::make_shared(); + + // Create temporary vertices + auto tmp_v1 = make_vertex(*local_graph); + tmp_v1->wcpt().point = path_points.front(); + tmp_v1->cluster(&cluster); + + auto tmp_v2 = make_vertex(*local_graph); + tmp_v2->wcpt().point = path_points.back(); + tmp_v2->cluster(&cluster); + + // Create temporary segment + auto tmp_sg = create_segment_for_cluster(cluster, dv, path_points); + if (!tmp_sg) { + if (max_vtx->wcpt().point.z() < min_vtx->wcpt().point.z()) { + temp_main_vertex = max_vtx; + } else { + temp_main_vertex = min_vtx; + } + return temp_main_vertex; + } + + // Add segment to local graph + add_segment(*local_graph, tmp_sg, tmp_v1, tmp_v2); + + // Create local fitter with same configuration as input fitter + TrackFitting local_fitter(TrackFitting::FittingType::Multiple); + local_fitter.set_parameters(track_fitter.get_parameters()); + local_fitter.add_graph(local_graph); + + // Do fitting on local graph + local_fitter.do_multi_tracking(true, true, false); + + // Create fit point cloud + create_segment_fit_point_cloud(tmp_sg, dv, "fit"); + + // Associate points from cluster to segment + clustering_points_segments({tmp_sg}, dv, "associate_points", 0.5*units::cm, 3.0); + + // Determine shower direction + bool has_direction = segment_determine_shower_direction(tmp_sg, particle_data, recomb_model, "associate_points"); + + double tmp_sg_length = segment_track_length(tmp_sg); + int tmp_sg_dir = tmp_sg->dirsign(); + + // Decide which vertex should be the main vertex based on direction + if (tmp_sg_dir == 1) { + temp_main_vertex = max_vtx; + } else if (tmp_sg_dir == -1) { + temp_main_vertex = min_vtx; + } else { + // No clear direction, pick forward vertex + if (max_vtx->wcpt().point.z() < min_vtx->wcpt().point.z()) { + temp_main_vertex = max_vtx; + } else { + temp_main_vertex = min_vtx; + } + } + + // For large showers, always pick forward vertex + if (tmp_sg_length > 80 * units::cm && + std::abs(max_vtx->wcpt().point.z() - min_vtx->wcpt().point.z()) > 40 * units::cm) { + if (max_vtx->wcpt().point.z() < min_vtx->wcpt().point.z()) { + temp_main_vertex = max_vtx; + } else { + temp_main_vertex = min_vtx; + } + } + + // Local graph and temporary elements automatically cleaned up when going out of scope + + return temp_main_vertex; +} \ No newline at end of file From 5275d788a4bb4fabac051c3c543aba10a6864ec5 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Fri, 2 Jan 2026 04:38:02 -0800 Subject: [PATCH 076/111] implement find_cont_muon_segment function --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 3 + clus/src/NeutrinoVertexFinder.cxx | 502 +++++++++++++++++++- 2 files changed, 503 insertions(+), 2 deletions(-) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 77741ab3..74274f47 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -104,6 +104,9 @@ namespace WireCell::Clus::PR { bool search_for_vertex_activities(Graph& graph, VertexPtr vertex, std::set& segments_set, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, double search_range = 1.5*units::cm); std::tuple examine_main_vertex_candidate(Graph& graph, VertexPtr vertex); VertexPtr compare_main_vertices_all_showers(Graph& graph, Facade::Cluster& cluster, std::vector& vertex_candidates, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); + float calc_conflict_maps(Graph& graph, VertexPtr vertex); + VertexPtr compare_main_vertices(Graph& graph, Facade::Cluster& cluster, std::vector& vertex_candidates); + std::pair find_cont_muon_segment(Graph &graph, SegmentPtr sg, VertexPtr vtx, bool flag_ignore_dQ_dx = false); // global information transfer void transfer_info_from_segment_to_cluster(Graph& graph, Facade::Cluster& cluster, const std::string& cloud_name = "associated_points"); diff --git a/clus/src/NeutrinoVertexFinder.cxx b/clus/src/NeutrinoVertexFinder.cxx index 03bda4fb..3886cdd5 100644 --- a/clus/src/NeutrinoVertexFinder.cxx +++ b/clus/src/NeutrinoVertexFinder.cxx @@ -1,5 +1,6 @@ #include "WireCellClus/NeutrinoPatternBase.h" #include "WireCellClus/PRSegmentFunctions.h" +#include "WireCellClus/FiducialUtils.h" using namespace WireCell::Clus::PR; using namespace WireCell::Clus; @@ -434,7 +435,7 @@ VertexPtr PatternAlgorithms::compare_main_vertices_all_showers(Graph& graph, Fac clustering_points_segments({tmp_sg}, dv, "associate_points", 0.5*units::cm, 3.0); // Determine shower direction - bool has_direction = segment_determine_shower_direction(tmp_sg, particle_data, recomb_model, "associate_points"); + segment_determine_shower_direction(tmp_sg, particle_data, recomb_model, "associate_points"); double tmp_sg_length = segment_track_length(tmp_sg); int tmp_sg_dir = tmp_sg->dirsign(); @@ -466,4 +467,501 @@ VertexPtr PatternAlgorithms::compare_main_vertices_all_showers(Graph& graph, Fac // Local graph and temporary elements automatically cleaned up when going out of scope return temp_main_vertex; -} \ No newline at end of file +} + +float PatternAlgorithms::calc_conflict_maps(Graph& graph, VertexPtr vertex){ + // Assume temp_vertex is true neutrino vertex, calculate conflicts in the system + float num_conflicts = 0; + + // Map segment to its direction (start vertex -> end vertex) + std::map> map_seg_dir; + std::set used_vertices; + + if (!vertex || !vertex->descriptor_valid()) return num_conflicts; + + // Start from the assumed neutrino vertex + std::vector> segments_to_be_examined; + auto vd = vertex->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr seg = graph[*e_it].segment; + if (seg) { + segments_to_be_examined.push_back(std::make_pair(vertex, seg)); + } + } + used_vertices.insert(vertex); + + // Propagate through the graph and build direction map + while (!segments_to_be_examined.empty()) { + std::vector> temp_segments; + + for (const auto& [prev_vtx, current_sg] : segments_to_be_examined) { + // Skip if already examined + if (map_seg_dir.find(current_sg) != map_seg_dir.end()) continue; + + // Find the other vertex of this segment + VertexPtr curr_vertex = find_other_vertex(graph, current_sg, prev_vtx); + if (!curr_vertex) continue; + + // Record the direction: prev_vtx -> curr_vertex + map_seg_dir[current_sg] = std::make_pair(prev_vtx, curr_vertex); + + // Skip if we've already processed this vertex + if (used_vertices.find(curr_vertex) != used_vertices.end()) continue; + + // Add all segments connected to curr_vertex for examination + if (curr_vertex->descriptor_valid()) { + auto curr_vd = curr_vertex->get_descriptor(); + auto curr_edge_range = boost::out_edges(curr_vd, graph); + for (auto e_it = curr_edge_range.first; e_it != curr_edge_range.second; ++e_it) { + SegmentPtr seg = graph[*e_it].segment; + if (seg) { + temp_segments.push_back(std::make_pair(curr_vertex, seg)); + } + } + } + used_vertices.insert(curr_vertex); + } + segments_to_be_examined = temp_segments; + } + + // Check segments for direction conflicts + for (const auto& [sg, vtx_pair] : map_seg_dir) { + VertexPtr start_vtx = vtx_pair.first; + + // Determine which end of segment connects to start_vtx + const auto& wcps = sg->wcpts(); + if (wcps.empty()) continue; + + bool flag_start = (ray_length(Ray{wcps.front().point, start_vtx->wcpt().point}) < + ray_length(Ray{wcps.back().point, start_vtx->wcpt().point})); + + // Check if segment has direction and is long enough to matter + int dir_sign = sg->dirsign(); + bool is_shower = sg->flags_any(SegmentFlags::kShowerTrajectory) || + sg->flags_any(SegmentFlags::kShowerTopology); + double sg_length = segment_track_length(sg); + + if (dir_sign != 0 && ((is_shower && sg_length > 5*units::cm) || !is_shower)) { + // Check if direction conflicts with topology + if ((flag_start && dir_sign == -1) || (!flag_start && dir_sign == 1)) { + if (!sg->dir_weak()) { + num_conflicts += 1.0; + } else { + num_conflicts += 0.5; + } + } + } + } + + // Beam direction (along z-axis) + Facade::geo_vector_t dir_beam(0, 0, 1); + + // Check vertices for topology conflicts + for (VertexPtr vtx : used_vertices) { + if (!vtx->descriptor_valid()) continue; + + auto vtx_vd = vtx->get_descriptor(); + auto vtx_edge_range = boost::out_edges(vtx_vd, graph); + + // Count number of segments + int num_segments = 0; + for (auto e_it = vtx_edge_range.first; e_it != vtx_edge_range.second; ++e_it) { + if (graph[*e_it].segment) num_segments++; + } + if (num_segments <= 1) continue; + + int n_in = 0; + int n_in_shower = 0; + int n_out_tracks = 0; + int n_out_showers = 0; + + std::map map_in_segment_dirs; + std::map map_out_segment_dirs; + + // Analyze each segment connected to this vertex + for (auto e_it = vtx_edge_range.first; e_it != vtx_edge_range.second; ++e_it) { + SegmentPtr sg = graph[*e_it].segment; + if (!sg || map_seg_dir.find(sg) == map_seg_dir.end()) continue; + + VertexPtr start_vtx = map_seg_dir[sg].first; + bool is_shower = sg->flags_any(SegmentFlags::kShowerTrajectory) || + sg->flags_any(SegmentFlags::kShowerTopology); + // bool is_shower_traj = sg->flags_any(SegmentFlags::kShowerTrajectory); + + if (vtx != start_vtx) { + // Segment is incoming to this vertex + n_in++; + if (is_shower) n_in_shower++; + map_in_segment_dirs[sg] = segment_cal_dir_3vector(sg, vtx->wcpt().point, 10*units::cm); + } else { + // Segment is outgoing from this vertex + if (!is_shower) { + n_out_tracks++; + } else { + n_out_showers++; + } + map_out_segment_dirs[sg] = segment_cal_dir_3vector(sg, vtx->wcpt().point, 10*units::cm); + } + } + + // Check angles between incoming and outgoing segments + if (!map_in_segment_dirs.empty() && !map_out_segment_dirs.empty()) { + double max_angle = -1; + SegmentPtr sg1 = nullptr; + SegmentPtr sg2 = nullptr; + + for (const auto& [in_sg, in_dir] : map_in_segment_dirs) { + for (const auto& [out_sg, out_dir] : map_out_segment_dirs) { + double angle = std::acos(std::clamp(in_dir.dot(out_dir) / + (in_dir.magnitude() * out_dir.magnitude()), -1.0, 1.0)) + * 180.0 / M_PI; + if (angle > max_angle) { + max_angle = angle; + sg1 = in_sg; + sg2 = out_sg; + } + } + } + + if (sg1 && sg2) { + bool flag_check = true; + bool is_sg2_shower_traj = sg2->flags_any(SegmentFlags::kShowerTrajectory); + bool is_sg1_shower = sg1->flags_any(SegmentFlags::kShowerTrajectory) || + sg1->flags_any(SegmentFlags::kShowerTopology); + bool is_sg2_shower = sg2->flags_any(SegmentFlags::kShowerTrajectory) || + sg2->flags_any(SegmentFlags::kShowerTopology); + + // Skip check for shower trajectories or both showers + if (is_sg2_shower_traj || (is_sg1_shower && is_sg2_shower)) { + flag_check = false; + } + + double angle_beam = std::acos(std::clamp(map_in_segment_dirs[sg1].dot(dir_beam) / + map_in_segment_dirs[sg1].magnitude(), -1.0, 1.0)) + * 180.0 / M_PI; + + if (max_angle >= 0 && flag_check) { + if (max_angle < 35) { + num_conflicts += 5.0; + } else if (max_angle < 70) { + num_conflicts += 3.0; + } else if (max_angle < 85) { + num_conflicts += 1.0; + } else if (max_angle < 110) { + num_conflicts += 0.25; + } + + // Additional penalty for backward-going particles + if (angle_beam < 60 && max_angle < 110) { + num_conflicts += 1.0; + } else if (angle_beam < 45 && max_angle < 70) { + num_conflicts += 3.0; + } + } + } + } + + // Penalize multiple incoming particles + if (n_in > 1) { + if (n_in != n_in_shower) { + num_conflicts += (n_in - 1); + } else { + num_conflicts += (n_in - 1) / 2.0; + } + } + + // Penalize showers in with tracks out (suspicious topology) + if (n_in_shower > 0 && n_out_tracks > 0) { + num_conflicts += std::min(n_in_shower, n_out_tracks); + } + (void)n_out_showers; // to avoid unused variable warning + } + + return num_conflicts; +} + +VertexPtr PatternAlgorithms::compare_main_vertices(Graph& graph, Facade::Cluster& cluster, std::vector& vertex_candidates){ + if (vertex_candidates.empty()) return nullptr; + + std::map map_vertex_num; + for (auto vtx : vertex_candidates) { + map_vertex_num[vtx] = 0; + } + + // Find the longest muon candidate + SegmentPtr max_length_muon = nullptr; + double max_length = 0; + + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr sg = graph[*eit].segment; + if (!sg || sg->cluster() != &cluster) continue; + + // Skip showers + bool is_shower = sg->flags_any(SegmentFlags::kShowerTrajectory) || + sg->flags_any(SegmentFlags::kShowerTopology); + if (is_shower) continue; + + // Skip protons + if (sg->has_particle_info() && std::abs(sg->particle_info()->pdg()) == 2212) continue; + + double length = segment_track_length(sg); + if (length > max_length) { + max_length = length; + max_length_muon = sg; + } + } + + // Analyze proton topology for each vertex candidate + for (auto vtx : vertex_candidates) { + if (!vtx->descriptor_valid()) continue; + + int n_proton_in = 0; + int n_proton_out = 0; + + auto vd = vtx->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr sg = graph[*e_it].segment; + if (!sg) continue; + + bool is_proton = sg->has_particle_info() && std::abs(sg->particle_info()->pdg()) == 2212; + if (!is_proton) continue; + + int dir_sign = sg->dirsign(); + bool is_weak = sg->dir_weak(); + + if ((is_weak || dir_sign == 0)) { + VertexPtr other_vertex = find_other_vertex(graph, sg, vtx); + if (!other_vertex || !other_vertex->descriptor_valid()) continue; + + auto other_vd = other_vertex->get_descriptor(); + auto other_edge_range = boost::out_edges(other_vd, graph); + + int num_segs = 0; + for (auto oe_it = other_edge_range.first; oe_it != other_edge_range.second; ++oe_it) { + if (graph[*oe_it].segment) num_segs++; + } + + if (num_segs > 1) { + for (auto oe_it = other_edge_range.first; oe_it != other_edge_range.second; ++oe_it) { + SegmentPtr other_sg = graph[*oe_it].segment; + if (!other_sg) continue; + + const auto& wcps = other_sg->wcpts(); + if (wcps.empty()) continue; + + bool flag_start = (ray_length(Ray{wcps.front().point, other_vertex->wcpt().point}) < + ray_length(Ray{wcps.back().point, other_vertex->wcpt().point})); + + int other_dir = other_sg->dirsign(); + bool other_weak = other_sg->dir_weak(); + bool is_other_proton = other_sg->has_particle_info() && + std::abs(other_sg->particle_info()->pdg()) == 2212; + + if (!other_weak && is_other_proton) { + if ((flag_start && other_dir == 1) || (!flag_start && other_dir == -1)) { + n_proton_out++; + } + if ((flag_start && other_dir == -1) || (!flag_start && other_dir == 1)) { + n_proton_in++; + } + } + + if ((other_weak || other_dir == 0) && is_other_proton) { + n_proton_in++; + } + } + } + } + } + + // Score proton topology + if (n_proton_in > n_proton_out) { + map_vertex_num[vtx] -= (n_proton_in - n_proton_out) / 4.0; + } else { + map_vertex_num[vtx] -= (n_proton_in - n_proton_out) / 4.0 - (n_proton_in + n_proton_out) / 8.0; + } + } + + // Score based on z position (prefer forward/upstream vertices) + double min_z = 1e9; + for (auto vtx : vertex_candidates) { + if (vtx->wcpt().point.z() < min_z) min_z = vtx->wcpt().point.z(); + } + + for (auto vtx : vertex_candidates) { + if (!vtx->descriptor_valid()) continue; + + // Position penalty + map_vertex_num[vtx] -= (vtx->wcpt().point.z() - min_z) / (200 * units::cm); + + // Score based on connected segments + auto vd = vtx->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr sg = graph[*e_it].segment; + if (!sg) continue; + + bool is_shower = sg->flags_any(SegmentFlags::kShowerTrajectory) || + sg->flags_any(SegmentFlags::kShowerTopology); + // bool is_shower_traj = sg->flags_any(SegmentFlags::kShowerTrajectory); + + if (is_shower) { + map_vertex_num[vtx] += 1.0 / 4.0 / 2.0; // number of showers + + auto pair_results = calculate_num_daughter_showers(graph, vtx, sg); + if (pair_results.second > 45 * units::cm) { + map_vertex_num[vtx] += 1.0 / 4.0 / 2.0; + } + } else { + map_vertex_num[vtx] += 1.0 / 4.0; // number of tracks + } + + int dir_sign = sg->dirsign(); + bool is_weak = sg->dir_weak(); + bool is_proton = sg->has_particle_info() && std::abs(sg->particle_info()->pdg()) == 2212; + + if (is_proton && dir_sign != 0 && !is_weak) { + map_vertex_num[vtx] += 1.0 / 4.0; // has a clear proton + } else if (dir_sign != 0 && !is_shower) { + map_vertex_num[vtx] += 1.0 / 4.0 / 2.0; // has direction with track + } + + if (max_length > 35 * units::cm && sg == max_length_muon) { + map_vertex_num[vtx] += 1.0 / 4.0 / 2.0; // long muon adds weight + } + } + } + + // Score based on fiducial volume (removed offset_x in WCP, need to validate) + auto fiducial_utils = cluster.grouping()->get_fiducialutils(); + if (fiducial_utils) { + for (auto vtx : vertex_candidates) { + if (fiducial_utils->inside_fiducial_volume(vtx->wcpt().point)) { + map_vertex_num[vtx] += 0.5; // good - inside fiducial volume + } + } + } + + // Score based on topology conflicts + for (auto vtx : vertex_candidates) { + double num_conflicts = calc_conflict_maps(graph, vtx); + map_vertex_num[vtx] -= num_conflicts / 4.0; + } + + // Find the vertex with maximum score + double max_val = -1e9; + VertexPtr max_vertex = nullptr; + + for (auto vtx : vertex_candidates) { + if (map_vertex_num[vtx] > max_val) { + max_val = map_vertex_num[vtx]; + max_vertex = vtx; + } + } + + return max_vertex; +} + + +std::pair PatternAlgorithms::find_cont_muon_segment(Graph &graph, SegmentPtr sg, VertexPtr vtx, bool flag_ignore_dQ_dx){ + SegmentPtr sg1 = nullptr; + VertexPtr vtx1 = nullptr; + + double max_length = 0; + double max_angle = 0; + double max_ratio = 0; + + bool flag_cont = false; + + double max_ratio1 = 0; + double max_ratio1_length = 0; + + double sg_length = segment_track_length(sg); + + // Get vertex point + WireCell::Point vtx_point = vtx->fit().valid() ? vtx->fit().point : vtx->wcpt().point; + + if (!vtx->descriptor_valid()) { + return std::make_pair(sg1, vtx1); + } + + // Iterate through all segments connected to this vertex + auto vd = vtx->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr sg2 = graph[*e_it].segment; + if (!sg2 || sg2 == sg) continue; + + // Find the other vertex of sg2 + VertexPtr vtx2 = find_other_vertex(graph, sg2, vtx); + if (!vtx2) continue; + + // Calculate direction vectors at 15cm from vertex + Facade::geo_vector_t dir1 = segment_cal_dir_3vector(sg, vtx_point, 15*units::cm); + Facade::geo_vector_t dir2 = segment_cal_dir_3vector(sg2, vtx_point, 15*units::cm); + + if (dir1.magnitude() == 0 || dir2.magnitude() == 0) continue; + + double length = segment_track_length(sg2); + + // Calculate angle (180° - angle between directions) + double cos_angle = std::clamp(dir1.dot(dir2) / (dir1.magnitude() * dir2.magnitude()), -1.0, 1.0); + double angle = (M_PI - std::acos(cos_angle)) / M_PI * 180.0; + + // Calculate dQ/dx ratio + double ratio = segment_median_dQ_dx(sg2) / (43e3 / units::cm); + + // For longer segments, also check angle at 50cm + double angle1 = angle; + if (length > 50*units::cm) { + Facade::geo_vector_t dir3 = segment_cal_dir_3vector(sg, vtx_point, 50*units::cm); + Facade::geo_vector_t dir4 = segment_cal_dir_3vector(sg2, vtx_point, 50*units::cm); + + if (dir3.magnitude() > 0 && dir4.magnitude() > 0) { + double cos_angle1 = std::clamp(dir3.dot(dir4) / (dir3.magnitude() * dir4.magnitude()), -1.0, 1.0); + angle1 = (M_PI - std::acos(cos_angle1)) / M_PI * 180.0; + } + } + + // Check if this segment qualifies as a continuation + bool angle_ok = (angle < 10.0 || angle1 < 10.0 || + (sg_length < 6*units::cm && (angle < 15.0 || angle1 < 15.0))); + bool ratio_ok = (ratio < 1.3 || flag_ignore_dQ_dx); + + if (angle_ok && ratio_ok) { + flag_cont = true; + + // Select segment with maximum projected length + double projected_length = length * std::cos(angle / 180.0 * M_PI); + if (projected_length > max_length) { + max_length = projected_length; + max_angle = angle; + max_ratio = ratio; + sg1 = sg2; + vtx1 = vtx2; + } + } else { + // Track maximum dQ/dx ratio among non-qualifying segments + if (ratio > max_ratio1) { + max_ratio1 = ratio; + max_ratio1_length = length; + } + } + } + + (void)max_angle; + (void)max_ratio; + (void)max_ratio1_length; + + if (flag_cont) { + return std::make_pair(sg1, vtx1); + } else { + return std::make_pair(nullptr, nullptr); + } +} From e22c0c48b5e3c7b000145830763e4e4e402c9ce7 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Fri, 2 Jan 2026 05:25:44 -0800 Subject: [PATCH 077/111] implement the examine_direction --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 2 + clus/src/NeutrinoVertexFinder.cxx | 534 ++++++++++++++++++++ util/inc/WireCellUtil/Flagged.h | 9 + 3 files changed, 545 insertions(+) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 74274f47..5e6b4358 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -107,6 +107,8 @@ namespace WireCell::Clus::PR { float calc_conflict_maps(Graph& graph, VertexPtr vertex); VertexPtr compare_main_vertices(Graph& graph, Facade::Cluster& cluster, std::vector& vertex_candidates); std::pair find_cont_muon_segment(Graph &graph, SegmentPtr sg, VertexPtr vtx, bool flag_ignore_dQ_dx = false); + bool examine_direction(Graph& graph, VertexPtr vertex, VertexPtr main_vertex, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model, bool flag_final = false); + // global information transfer void transfer_info_from_segment_to_cluster(Graph& graph, Facade::Cluster& cluster, const std::string& cloud_name = "associated_points"); diff --git a/clus/src/NeutrinoVertexFinder.cxx b/clus/src/NeutrinoVertexFinder.cxx index 3886cdd5..d46182f6 100644 --- a/clus/src/NeutrinoVertexFinder.cxx +++ b/clus/src/NeutrinoVertexFinder.cxx @@ -965,3 +965,537 @@ std::pair PatternAlgorithms::find_cont_muon_segment(Graph return std::make_pair(nullptr, nullptr); } } + +bool PatternAlgorithms::examine_direction(Graph& graph, VertexPtr vertex, VertexPtr main_vertex, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model, bool flag_final){ + if (!vertex || !vertex->cluster()) return false; + + Facade::Cluster& cluster = *vertex->cluster(); + + // Calculate cluster statistics + double max_vtx_length = 0; + double min_vtx_length = 1e9; + int num_total_segments = 0; + bool flag_only_showers = true; + + // Examine vertex segments to determine characteristics + if (vertex->descriptor_valid()) { + auto vd = vertex->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr seg = graph[*e_it].segment; + if (!seg) continue; + + double length = segment_track_length(seg); + if (length > max_vtx_length) max_vtx_length = length; + if (length < min_vtx_length) min_vtx_length = length; + } + } + + // Check all vertices in the cluster + auto [vbegin, vend] = boost::vertices(graph); + for (auto vit = vbegin; vit != vend; ++vit) { + VertexPtr vtx = graph[*vit].vertex; + if (!vtx || vtx->cluster() != &cluster) continue; + + auto results = examine_main_vertex_candidate(graph, vtx); + bool flag_in = std::get<0>(results); + int ntracks = std::get<1>(results); + // int nshowers = std::get<2>(results); + + if (!flag_in && ntracks > 0) { + flag_only_showers = false; + } + } + + // Count total segments in cluster + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr seg = graph[*eit].segment; + if (seg && seg->cluster() == &cluster) { + num_total_segments++; + } + } + + // Determine if only showers based on topology + if (vertex->descriptor_valid()) { + auto vd = vertex->get_descriptor(); + int num_vertex_segments = 0; + auto edge_range = boost::out_edges(vd, graph); + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + if (graph[*e_it].segment) num_vertex_segments++; + } + + if ((num_vertex_segments == 2 && (max_vtx_length > 30*units::cm || min_vtx_length > 15*units::cm)) || + (num_vertex_segments > 2 && num_total_segments > 4) || + (num_vertex_segments > 3)) { + flag_only_showers = false; + } + } + + // Beam direction (along z-axis) + Facade::geo_vector_t drift_dir(1, 0, 0); + + // Track used vertices and segments + std::set used_vertices; + std::set used_segments; + + // Start propagation from the main vertex + std::vector> segments_to_be_examined; + if (vertex->descriptor_valid()) { + auto vd = vertex->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr seg = graph[*e_it].segment; + if (seg) { + segments_to_be_examined.push_back(std::make_pair(vertex, seg)); + } + } + } + used_vertices.insert(vertex); + + // Propagate through the graph setting directions and particle types + while (!segments_to_be_examined.empty()) { + std::vector> temp_segments; + + for (const auto& [prev_vtx, current_sg] : segments_to_be_examined) { + if (!prev_vtx->descriptor_valid()) continue; + + // Check for incoming showers + bool flag_shower_in = false; + std::vector in_showers; + + auto prev_vd = prev_vtx->get_descriptor(); + auto prev_edge_range = boost::out_edges(prev_vd, graph); + for (auto e_it = prev_edge_range.first; e_it != prev_edge_range.second; ++e_it) { + SegmentPtr sg = graph[*e_it].segment; + if (!sg) continue; + + const auto& wcps = sg->wcpts(); + if (wcps.empty()) continue; + + bool flag_start = (ray_length(Ray{wcps.front().point, prev_vtx->wcpt().point}) < + ray_length(Ray{wcps.back().point, prev_vtx->wcpt().point})); + + int dir_sign = sg->dirsign(); + if ((flag_start && dir_sign == -1) || (!flag_start && dir_sign == 1)) { + if (sg->flags_any(SegmentFlags::kShowerTrajectory) || sg->flags_any(SegmentFlags::kShowerTopology)) { + flag_shower_in = true; + in_showers.push_back(sg); + break; + } + } + } + + if (used_segments.find(current_sg) != used_segments.end()) continue; + + double length = segment_track_length(current_sg); + bool is_shower = current_sg->flags_any(SegmentFlags::kShowerTrajectory) || + current_sg->flags_any(SegmentFlags::kShowerTopology); + + // Determine segment direction + if (current_sg->dirsign() == 0 || current_sg->dir_weak() || is_shower || flag_final) { + const auto& wcps = current_sg->wcpts(); + if (!wcps.empty()) { + bool flag_start = (ray_length(Ray{wcps.front().point, prev_vtx->wcpt().point}) < + ray_length(Ray{wcps.back().point, prev_vtx->wcpt().point})); + + // Set direction + if (flag_start) { + current_sg->dirsign(1); + } else { + current_sg->dirsign(-1); + } + + // Determine particle type + if (flag_shower_in && current_sg->dirsign() == 0 && !is_shower) { + segment_cal_4mom(current_sg, 11, particle_data, recomb_model); + } else if (flag_shower_in && length < 2.0*units::cm && !is_shower) { + segment_cal_4mom(current_sg, 11, particle_data, recomb_model); + } else if (flag_shower_in && current_sg->has_particle_info() && + (std::abs(current_sg->particle_info()->pdg()) == 13 || current_sg->particle_info()->pdg() == 0)) { + segment_cal_4mom(current_sg, 11, particle_data, recomb_model); + } else { + auto pair_result = calculate_num_daughter_showers(graph, prev_vtx, current_sg); + auto pair_result1 = calculate_num_daughter_showers(graph, prev_vtx, current_sg, false); + int num_daughter_showers = pair_result.first; + double length_daughter_showers = pair_result.second; + + // Check if should be electron based on daughter showers + int current_pdg = current_sg->has_particle_info() ? current_sg->particle_info()->pdg() : 0; + if (current_pdg != 11 && + (num_daughter_showers >= 4 || + (length_daughter_showers > 50*units::cm && num_daughter_showers >= 2)) && + pair_result.second > pair_result1.second - length - pair_result.second) { + + // Check angles with connected segments + bool flag_change = false; + VertexPtr next_vertex = find_other_vertex(graph, current_sg, prev_vtx); + if (next_vertex && next_vertex->descriptor_valid()) { + Facade::geo_vector_t tmp_dir1 = segment_cal_dir_3vector(current_sg, next_vertex->wcpt().point, 15*units::cm); + + auto next_vd = next_vertex->get_descriptor(); + auto next_edge_range = boost::out_edges(next_vd, graph); + for (auto ne_it = next_edge_range.first; ne_it != next_edge_range.second; ++ne_it) { + SegmentPtr other_sg = graph[*ne_it].segment; + if (!other_sg || other_sg == current_sg) continue; + + Facade::geo_vector_t tmp_dir2 = segment_cal_dir_3vector(other_sg, next_vertex->wcpt().point, 15*units::cm); + + if (tmp_dir1.magnitude() > 0 && tmp_dir2.magnitude() > 0) { + double angle = std::acos(std::clamp(tmp_dir1.dot(tmp_dir2) / + (tmp_dir1.magnitude() * tmp_dir2.magnitude()), -1.0, 1.0)) + * 180.0 / M_PI; + double angle_drift1 = std::acos(std::clamp(drift_dir.dot(tmp_dir1) / + (drift_dir.magnitude() * tmp_dir1.magnitude()), -1.0, 1.0)) + * 180.0 / M_PI; + double angle_drift2 = std::acos(std::clamp(drift_dir.dot(tmp_dir2) / + (drift_dir.magnitude() * tmp_dir2.magnitude()), -1.0, 1.0)) + * 180.0 / M_PI; + double other_length = segment_track_length(other_sg); + + if (angle > 155 || + (angle > 135 && std::abs(angle_drift1 - 90) < 10 && std::abs(angle_drift2 - 90) < 10) || + (angle > 135 && other_length < 6*units::cm)) { + flag_change = true; + break; + } + } + } + } + + if (flag_change) { + segment_cal_4mom(current_sg, 11, particle_data, recomb_model); + } + } else if (current_pdg == 11 && num_daughter_showers <= 2 && !flag_shower_in && + !current_sg->flags_any(SegmentFlags::kShowerTopology) && + !current_sg->flags_any(SegmentFlags::kShowerTrajectory) && + length > 10*units::cm && !flag_only_showers) { + + double direct_length = segment_track_direct_length(current_sg); + if (direct_length >= 34*units::cm || + (direct_length < 34*units::cm && direct_length > 0.93 * length)) { + segment_cal_4mom(current_sg, 13, particle_data, recomb_model); + } + } else if (current_pdg == 11 && current_sg->flags_any(SegmentFlags::kShowerTrajectory) && + num_daughter_showers == 1 && !flag_only_showers) { + auto pair_result1 = calculate_num_daughter_showers(graph, prev_vtx, current_sg, false); + if (pair_result1.second > 3*length && pair_result1.second - length > 12*units::cm) { + current_sg->unset_flags(SegmentFlags::kShowerTrajectory); + segment_cal_4mom(current_sg, 13, particle_data, recomb_model); + } + } + } + + // Default particle type assignment for undetermined particles from main vertex + if (vertex == main_vertex) { + int current_pdg = current_sg->has_particle_info() ? current_sg->particle_info()->pdg() : 0; + if (current_pdg == 0 && !is_shower) { + if (flag_only_showers) { + segment_cal_4mom(current_sg, 11, particle_data, recomb_model); + } else { + double dqdx_ratio = segment_median_dQ_dx(current_sg) / (43e3 / units::cm); + if (dqdx_ratio > 1.4) { + segment_cal_4mom(current_sg, 2212, particle_data, recomb_model); + } else { + segment_cal_4mom(current_sg, 13, particle_data, recomb_model); + } + } + } + } + + current_sg->dir_weak(true); + } + } else if (current_sg->dirsign() != 0 && !current_sg->dir_weak()) { + // Strong direction already set + auto pair_result = calculate_num_daughter_showers(graph, prev_vtx, current_sg); + int num_daughter_showers = pair_result.first; + + int current_pdg = current_sg->has_particle_info() ? current_sg->particle_info()->pdg() : 0; + if (current_pdg == 2212 && flag_shower_in && num_daughter_showers == 0) { + for (auto in_shower : in_showers) { + double dqdx_ratio = segment_median_dQ_dx(in_shower) / (43e3 / units::cm); + if (dqdx_ratio > 1.3) { + segment_cal_4mom(in_shower, 2212, particle_data, recomb_model); + } else { + segment_cal_4mom(in_shower, 211, particle_data, recomb_model); + } + in_shower->unset_flags(SegmentFlags::kShowerTrajectory); + in_shower->unset_flags(SegmentFlags::kShowerTopology); + } + } + } + + used_segments.insert(current_sg); + + // Find next vertex and add its segments to examination list + VertexPtr curr_vertex = find_other_vertex(graph, current_sg, prev_vtx); + if (!curr_vertex || used_vertices.find(curr_vertex) != used_vertices.end()) continue; + + if (curr_vertex->descriptor_valid()) { + auto curr_vd = curr_vertex->get_descriptor(); + auto curr_edge_range = boost::out_edges(curr_vd, graph); + for (auto ce_it = curr_edge_range.first; ce_it != curr_edge_range.second; ++ce_it) { + SegmentPtr seg = graph[*ce_it].segment; + if (seg) { + temp_segments.push_back(std::make_pair(curr_vertex, seg)); + } + } + } + used_vertices.insert(curr_vertex); + } + segments_to_be_examined = temp_segments; + } + + // Find long muon candidates + bool flag_fill_long_muon = true; + for (auto seg : segments_in_long_muon) { + if (seg->cluster() == &cluster) { + flag_fill_long_muon = false; + break; + } + } + + if (flag_fill_long_muon && vertex->descriptor_valid()) { + auto vd = vertex->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr sg = graph[*e_it].segment; + if (!sg) continue; + + double dqdx_ratio = segment_median_dQ_dx(sg) / (43e3 / units::cm); + if (dqdx_ratio > 1.3) continue; + + VertexPtr vtx = find_other_vertex(graph, sg, vertex); + if (!vtx) continue; + + std::vector acc_segments; + std::vector acc_vertices; + acc_segments.push_back(sg); + acc_vertices.push_back(vtx); + + auto results = find_cont_muon_segment(graph, sg, vtx); + while (results.first != nullptr) { + acc_segments.push_back(results.first); + acc_vertices.push_back(results.second); + results = find_cont_muon_segment(graph, results.first, results.second); + } + + double total_length = 0, max_length = 0; + for (auto acc_seg : acc_segments) { + double length = segment_track_length(acc_seg); + total_length += length; + if (length > max_length) max_length = length; + } + + if (total_length > 45*units::cm && max_length > 35*units::cm && acc_segments.size() > 1) { + for (auto acc_seg : acc_segments) { + segment_cal_4mom(acc_seg, 13, particle_data, recomb_model); + acc_seg->unset_flags(SegmentFlags::kShowerTrajectory); + acc_seg->unset_flags(SegmentFlags::kShowerTopology); + segments_in_long_muon.insert(acc_seg); + } + for (auto acc_vtx : acc_vertices) { + vertices_in_long_muon.insert(acc_vtx); + } + } + } + } + + // Find muon candidate and make others pions + if (vertex->descriptor_valid()) { + SegmentPtr muon_sg = nullptr; + double muon_length = 0; + std::vector pion_sgs; + + auto vd = vertex->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr sg = graph[*e_it].segment; + if (!sg) continue; + + int pdg = sg->has_particle_info() ? sg->particle_info()->pdg() : 0; + if (std::abs(pdg) == 13) { + if (segments_in_long_muon.find(sg) != segments_in_long_muon.end()) continue; + + VertexPtr other_vertex = find_other_vertex(graph, sg, vertex); + if (!other_vertex || !other_vertex->descriptor_valid()) continue; + + int n_proton = 0; + auto other_vd = other_vertex->get_descriptor(); + auto other_edge_range = boost::out_edges(other_vd, graph); + for (auto oe_it = other_edge_range.first; oe_it != other_edge_range.second; ++oe_it) { + SegmentPtr other_sg = graph[*oe_it].segment; + if (other_sg && other_sg->has_particle_info() && + std::abs(other_sg->particle_info()->pdg()) == 2212) { + n_proton++; + } + } + + double sg_length = segment_track_length(sg); + if (sg_length > muon_length && n_proton == 0) { + muon_length = sg_length; + muon_sg = sg; + } + pion_sgs.push_back(sg); + } else if (pdg == 0) { + VertexPtr other_vertex = find_other_vertex(graph, sg, vertex); + if (!other_vertex || !other_vertex->descriptor_valid()) continue; + + int n_proton = 0; + auto other_vd = other_vertex->get_descriptor(); + auto other_edge_range = boost::out_edges(other_vd, graph); + for (auto oe_it = other_edge_range.first; oe_it != other_edge_range.second; ++oe_it) { + SegmentPtr other_sg = graph[*oe_it].segment; + if (other_sg && other_sg->has_particle_info() && + std::abs(other_sg->particle_info()->pdg()) == 2212) { + n_proton++; + } + } + + if (n_proton > 0) { + double dqdx_ratio = segment_median_dQ_dx(sg) / (43e3 / units::cm); + if (dqdx_ratio > 1.3) { + segment_cal_4mom(sg, 2212, particle_data, recomb_model); + } else { + segment_cal_4mom(sg, 211, particle_data, recomb_model); + } + } + } + } + + // Convert non-muon candidates to pions + for (auto pion_sg : pion_sgs) { + if (pion_sg == muon_sg) continue; + segment_cal_4mom(pion_sg, 211, particle_data, recomb_model); + } + } + + // Find Michel electrons + auto [ebegin2, eend2] = boost::edges(graph); + for (auto eit = ebegin2; eit != eend2; ++eit) { + SegmentPtr sg = graph[*eit].segment; + if (!sg || sg->cluster() != &cluster) continue; + + // Check if segment has particle info with mass but no 4-momentum yet, and is not shower topology + bool has_4mom = sg->has_particle_info(); + if (has_4mom && sg->particle_info()->mass() > 0 && + !sg->flags_any(SegmentFlags::kShowerTopology)) { + + if (!sg->dir_weak()) { + // Strong direction - calculate 4-momentum + int pdg = sg->particle_info()->pdg(); + segment_cal_4mom(sg, pdg, particle_data, recomb_model); + } else { + // Weak direction - need to check endpoint conditions + // Find the two vertices of this segment + VertexPtr start_v = nullptr, end_v = nullptr; + + auto [vbegin, vend] = boost::vertices(graph); + for (auto vit = vbegin; vit != vend; ++vit) { + VertexPtr vtx = graph[*vit].vertex; + if (!vtx || !vtx->descriptor_valid()) continue; + + // Check if this vertex is connected to our segment + auto vtx_vd = vtx->get_descriptor(); + auto vtx_edge_range = boost::out_edges(vtx_vd, graph); + for (auto ve_it = vtx_edge_range.first; ve_it != vtx_edge_range.second; ++ve_it) { + if (graph[*ve_it].segment == sg) { + // This vertex is connected to our segment + const auto& wcps = sg->wcpts(); + if (!wcps.empty()) { + if (ray_length(Ray{wcps.front().point, vtx->wcpt().point}) < 0.01*units::cm) { + start_v = vtx; + } else if (ray_length(Ray{wcps.back().point, vtx->wcpt().point}) < 0.01*units::cm) { + end_v = vtx; + } + } + break; + } + } + if (start_v && end_v) break; + } + + if (!start_v || !end_v) continue; + + int dir_sign = sg->dirsign(); + auto fiducial_utils = cluster.grouping()->get_fiducialutils(); + + // Count segments at start and end vertices + int num_segs_start = 0, num_segs_end = 0; + if (start_v->descriptor_valid()) { + auto start_vd = start_v->get_descriptor(); + auto start_edge_range = boost::out_edges(start_vd, graph); + for (auto se_it = start_edge_range.first; se_it != start_edge_range.second; ++se_it) { + if (graph[*se_it].segment) num_segs_start++; + } + } + if (end_v->descriptor_valid()) { + auto end_vd = end_v->get_descriptor(); + auto end_edge_range = boost::out_edges(end_vd, graph); + for (auto ee_it = end_edge_range.first; ee_it != end_edge_range.second; ++ee_it) { + if (graph[*ee_it].segment) num_segs_end++; + } + } + + // Check if endpoint is in fiducial volume or is Michel electron candidate + WireCell::Point end_pt = end_v->fit().valid() ? end_v->fit().point : end_v->wcpt().point; + WireCell::Point start_pt = start_v->fit().valid() ? start_v->fit().point : start_v->wcpt().point; + + bool should_calc = false; + + // Case 1: Direction is outward and endpoint is isolated in fiducial volume + if (dir_sign == 1 && num_segs_end == 1 && fiducial_utils && + fiducial_utils->inside_fiducial_volume(end_pt)) { + should_calc = true; + } else if (dir_sign == -1 && num_segs_start == 1 && fiducial_utils && + fiducial_utils->inside_fiducial_volume(start_pt)) { + should_calc = true; + } + // Case 2: Check for Michel electron topology at end vertex (2 segments, one is shower) + else if (num_segs_end == 2 && end_v->descriptor_valid()) { + bool flag_Michel = false; + auto end_vd = end_v->get_descriptor(); + auto end_edge_range = boost::out_edges(end_vd, graph); + for (auto ee_it = end_edge_range.first; ee_it != end_edge_range.second; ++ee_it) { + SegmentPtr other_sg = graph[*ee_it].segment; + if (!other_sg || other_sg == sg) continue; + // Check if other segment is a shower (kShowerTrajectory flag or electron PDG) + if (other_sg->flags_any(SegmentFlags::kShowerTrajectory) || + (other_sg->has_particle_info() && std::abs(other_sg->particle_info()->pdg()) == 11)) { + flag_Michel = true; + break; + } + } + if (flag_Michel) should_calc = true; + } + // Case 3: Check for Michel electron topology at start vertex (2 segments, one is shower) + else if (num_segs_start == 2 && start_v->descriptor_valid()) { + bool flag_Michel = false; + auto start_vd = start_v->get_descriptor(); + auto start_edge_range = boost::out_edges(start_vd, graph); + for (auto se_it = start_edge_range.first; se_it != start_edge_range.second; ++se_it) { + SegmentPtr other_sg = graph[*se_it].segment; + if (!other_sg || other_sg == sg) continue; + // Check if other segment is a shower (kShowerTrajectory flag or electron PDG) + if (other_sg->flags_any(SegmentFlags::kShowerTrajectory) || + (other_sg->has_particle_info() && std::abs(other_sg->particle_info()->pdg()) == 11)) { + flag_Michel = true; + break; + } + } + if (flag_Michel) should_calc = true; + } + + if (should_calc) { + int pdg = sg->particle_info()->pdg(); + segment_cal_4mom(sg, pdg, particle_data, recomb_model); + } + } + } + } + + return examine_maps(graph, cluster); +} + diff --git a/util/inc/WireCellUtil/Flagged.h b/util/inc/WireCellUtil/Flagged.h index 91dcf9f9..c7d78aa2 100644 --- a/util/inc/WireCellUtil/Flagged.h +++ b/util/inc/WireCellUtil/Flagged.h @@ -56,10 +56,19 @@ namespace WireCell { if (index < max_flags) { m_flags |= ( 1ULL << index); }; } + /// Clear a specific bit to false. If index is out of bounds, this is a quiet no-op. + void unset_flag(index_t index) { + if (index < max_flags) { m_flags &= ~( 1ULL << index); }; + } + /// Set any number of flags. The bits set in "flags" are set in our /// internal flags. Any existing set flags are kept. void set_flags(FlagsType flags) { m_flags |= static_cast(flags); } + /// Clear any number of flags. The bits set in "flags" are cleared in our + /// internal flags. Other existing flags are kept. + void unset_flags(FlagsType flags) { m_flags &= ~static_cast(flags); } + /// Keep only the set flags in "flags" that are also set in our flags. /// New flags are not set, this disable existing flags not in set in the /// input. From 85b0c8da102b55a59a8f43f156a7162ee045ec65 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Fri, 2 Jan 2026 06:16:11 -0800 Subject: [PATCH 078/111] implement the eliminate_short_vertex_activities --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 1 + clus/src/NeutrinoVertexFinder.cxx | 210 ++++++++++++++++++++ 2 files changed, 211 insertions(+) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 5e6b4358..d888d3a0 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -102,6 +102,7 @@ namespace WireCell::Clus::PR { // vertex related functions bool search_for_vertex_activities(Graph& graph, VertexPtr vertex, std::set& segments_set, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, double search_range = 1.5*units::cm); + bool eliminate_short_vertex_activities(Graph& graph, Facade::Cluster& cluster, VertexPtr main_vertex, std::set& existing_segments, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); std::tuple examine_main_vertex_candidate(Graph& graph, VertexPtr vertex); VertexPtr compare_main_vertices_all_showers(Graph& graph, Facade::Cluster& cluster, std::vector& vertex_candidates, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); float calc_conflict_maps(Graph& graph, VertexPtr vertex); diff --git a/clus/src/NeutrinoVertexFinder.cxx b/clus/src/NeutrinoVertexFinder.cxx index d46182f6..a62d2af7 100644 --- a/clus/src/NeutrinoVertexFinder.cxx +++ b/clus/src/NeutrinoVertexFinder.cxx @@ -1499,3 +1499,213 @@ bool PatternAlgorithms::examine_direction(Graph& graph, VertexPtr vertex, Vertex return examine_maps(graph, cluster); } +bool PatternAlgorithms::eliminate_short_vertex_activities(Graph& graph, Facade::Cluster& cluster, VertexPtr main_vertex, std::set& existing_segments, TrackFitting& track_fitter, IDetectorVolumes::pointer dv){ + bool flag_updated = false; + bool flag_continue = true; + + while (flag_continue) { + flag_continue = false; + std::set to_be_removed_segments; + std::set to_be_removed_vertices; + + // Iterate through all edges (segments) in the graph + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr sg = graph[*eit].segment; + if (!sg || sg->cluster() != &cluster) continue; + if (existing_segments.find(sg) != existing_segments.end()) continue; + + // Get the two vertices of this segment + auto [vbegin, vend] = boost::vertices(graph); + VertexPtr v1 = nullptr, v2 = nullptr; + + // Find vertices connected to this segment + for (auto vit = vbegin; vit != vend; ++vit) { + VertexPtr vtx = graph[*vit].vertex; + if (!vtx || !vtx->descriptor_valid()) continue; + + auto vtx_vd = vtx->get_descriptor(); + auto vtx_edge_range = boost::out_edges(vtx_vd, graph); + for (auto ve_it = vtx_edge_range.first; ve_it != vtx_edge_range.second; ++ve_it) { + if (graph[*ve_it].segment == sg) { + if (!v1) v1 = vtx; + else if (!v2 && vtx != v1) v2 = vtx; + break; + } + } + if (v1 && v2) break; + } + + if (!v1 || !v2) continue; + + // Count segments at each vertex + int num_segs_v1 = 0, num_segs_v2 = 0; + if (v1->descriptor_valid()) { + auto v1d = v1->get_descriptor(); + auto v1_edge_range = boost::out_edges(v1d, graph); + for (auto ve_it = v1_edge_range.first; ve_it != v1_edge_range.second; ++ve_it) { + if (graph[*ve_it].segment) num_segs_v1++; + } + } + if (v2->descriptor_valid()) { + auto v2d = v2->get_descriptor(); + auto v2_edge_range = boost::out_edges(v2d, graph); + for (auto ve_it = v2_edge_range.first; ve_it != v2_edge_range.second; ++ve_it) { + if (graph[*ve_it].segment) num_segs_v2++; + } + } + + double length = segment_track_direct_length(sg); + + // Check Case 1: v1 has 1 segment, v2 has >=3 segments + if (num_segs_v1 == 1 && num_segs_v2 >= 3) { + if (length < 0.36*units::cm) { + to_be_removed_segments.insert(sg); + to_be_removed_vertices.insert(v1); + flag_continue = true; + break; + } else if (length < 0.5*units::cm && num_segs_v2 > 3) { + to_be_removed_segments.insert(sg); + to_be_removed_vertices.insert(v1); + flag_continue = true; + break; + } + } + // Check Case 2: v2 has 1 segment, v1 has >=3 segments + else if (num_segs_v2 == 1 && num_segs_v1 >= 3) { + if (length < 0.36*units::cm) { + to_be_removed_segments.insert(sg); + to_be_removed_vertices.insert(v2); + flag_continue = true; + break; + } else if (length < 0.5*units::cm && num_segs_v1 > 3) { + to_be_removed_segments.insert(sg); + to_be_removed_vertices.insert(v2); + flag_continue = true; + break; + } + } + + // Check Case 3: Very short segments (< 0.1 cm) or segments connected to main_vertex + if (!flag_continue) { + if ((v1 == main_vertex && num_segs_v1 > 1) || (v2 == main_vertex && num_segs_v2 > 1)) { + if (length < 0.1*units::cm) { + to_be_removed_segments.insert(sg); + to_be_removed_vertices.insert(v2); + flag_continue = true; + break; + } + } + } + + // Check Case 4: Isolated vertex close to another segment + if (!flag_continue) { + WireCell::Point v1_pt = v1->fit().valid() ? v1->fit().point : v1->wcpt().point; + WireCell::Point v2_pt = v2->fit().valid() ? v2->fit().point : v2->wcpt().point; + // auto v1_wpid = dv->contained_by(v1_pt); + // auto v2_wpid = dv->contained_by(v2_pt); + + if (num_segs_v1 == 1 && num_segs_v2 > 1 && v2->descriptor_valid()) { + auto v2d = v2->get_descriptor(); + auto v2_edge_range = boost::out_edges(v2d, graph); + for (auto ve_it = v2_edge_range.first; ve_it != v2_edge_range.second; ++ve_it) { + SegmentPtr sg1 = graph[*ve_it].segment; + if (!sg1 || sg1 == sg) continue; + + auto [dis, closest_pt] = segment_get_closest_point(sg1, v1_pt, "fit"); + double seg_length = segment_track_length(sg); + + if (dis < 0.36*units::cm) { + to_be_removed_segments.insert(sg); + to_be_removed_vertices.insert(v1); + flag_continue = true; + break; + } else if ((v2 == main_vertex && dis < 0.45*units::cm && seg_length < 0.45*units::cm)) { + to_be_removed_segments.insert(sg); + to_be_removed_vertices.insert(v1); + flag_continue = true; + break; + } + } + } else if (num_segs_v2 == 1 && num_segs_v1 > 1 && v1->descriptor_valid()) { + auto v1d = v1->get_descriptor(); + auto v1_edge_range = boost::out_edges(v1d, graph); + for (auto ve_it = v1_edge_range.first; ve_it != v1_edge_range.second; ++ve_it) { + SegmentPtr sg1 = graph[*ve_it].segment; + if (!sg1 || sg1 == sg) continue; + + auto [dis, closest_pt] = segment_get_closest_point(sg1, v2_pt, "fit"); + double seg_length = segment_track_length(sg); + + if (dis < 0.36*units::cm) { + to_be_removed_segments.insert(sg); + to_be_removed_vertices.insert(v2); + flag_continue = true; + break; + } else if ((v1 == main_vertex && dis < 0.45*units::cm && seg_length < 0.45*units::cm)) { + to_be_removed_segments.insert(sg); + to_be_removed_vertices.insert(v2); + flag_continue = true; + break; + } + } + } + } + + // Check Case 5: Segment not in existing_segments and all points close to existing segments + if (!flag_continue && existing_segments.find(sg) == existing_segments.end() && length > 0.45*units::cm) { + const auto& wcpts = sg->wcpts(); + int n_good = 0; + + for (size_t i = 0; i < wcpts.size(); i++) { + WireCell::Point pt = wcpts[i].point; + auto wpid = dv->contained_by(pt); + if (wpid.face() == -1 || wpid.apa() == -1) continue; + + double dis_u = 1e9, dis_v = 1e9, dis_w = 1e9; + + for (auto existing_sg : existing_segments) { + // Check if segment exists in graph + bool seg_exists = false; + auto [ebegin2, eend2] = boost::edges(graph); + for (auto eit2 = ebegin2; eit2 != eend2; ++eit2) { + if (graph[*eit2].segment == existing_sg) { + seg_exists = true; + break; + } + } + if (!seg_exists) continue; + + auto [dist_u, dist_v, dist_w] = segment_get_closest_2d_distances(existing_sg, pt, wpid.apa(), wpid.face(), "fit"); + if (dist_u < dis_u) dis_u = dist_u; + if (dist_v < dis_v) dis_v = dist_v; + if (dist_w < dis_w) dis_w = dist_w; + } + + if ((dis_u > 0.45*units::cm || dis_v > 0.45*units::cm || dis_w > 0.45*units::cm)) { + n_good++; + } + } + + if (n_good == 0) { + to_be_removed_segments.insert(sg); + if (num_segs_v1 == 1) to_be_removed_vertices.insert(v1); + if (num_segs_v2 == 1) to_be_removed_vertices.insert(v2); + } + } + + if (flag_continue) break; + } + + // Remove segments and vertices + for (auto sg : to_be_removed_segments) { + flag_updated = true; + remove_segment(graph, sg); + } + for (auto vtx : to_be_removed_vertices) { + remove_vertex(graph, vtx); + } + } + + return flag_updated; +} From 368986c72ee7b7a0b0d5dbd27caae416bf92c04d Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Fri, 2 Jan 2026 08:16:51 -0800 Subject: [PATCH 079/111] update MyFCN --- clus/inc/WireCellClus/MyFCN.h | 109 ++++++++++++ clus/inc/WireCellClus/PRSegment.h | 5 + clus/src/MyFCN.cxx | 283 ++++++++++++++++++++++++++++++ clus/src/PRSegment.cxx | 54 ++++++ 4 files changed, 451 insertions(+) create mode 100644 clus/inc/WireCellClus/MyFCN.h create mode 100644 clus/src/MyFCN.cxx diff --git a/clus/inc/WireCellClus/MyFCN.h b/clus/inc/WireCellClus/MyFCN.h new file mode 100644 index 00000000..c06facd8 --- /dev/null +++ b/clus/inc/WireCellClus/MyFCN.h @@ -0,0 +1,109 @@ +#ifndef WIRECELLCLUS_MYFCN_H +#define WIRECELLCLUS_MYFCN_H + +#include "WireCellClus/PRGraph.h" +#include "WireCellClus/Facade_Cluster.h" +#include "WireCellUtil/Units.h" + +#include +#include + +namespace WireCell::Clus::PR { + + /// MyFCN: Vertex fitting class for pattern recognition + /// Performs vertex position fitting by minimizing distances from segments + class MyFCN { + public: + /// Constructor + /// @param vtx The vertex to fit + /// @param flag_vtx_constraint Whether to constrain the vertex position + /// @param vtx_constraint_range Range for vertex constraint + /// @param vertex_protect_dis Protection distance for vertex + /// @param vertex_protect_dis_short_track Protection distance for short tracks + /// @param fit_dis Fitting distance + MyFCN(VertexPtr vtx, + bool flag_vtx_constraint = false, + double vtx_constraint_range = 1*units::cm, + double vertex_protect_dis = 1.5*units::cm, + double vertex_protect_dis_short_track = 0.9*units::cm, + double fit_dis = 6*units::cm); + + ~MyFCN(); + + /// Update the fitting range parameters + void update_fit_range(double tmp_vertex_protect_dis = 1.5*units::cm, + double tmp_vertex_protect_dis_short_track = 0.9*units::cm, + double tmp_fit_dis = 6*units::cm); + + /// Add a segment to the fitting + void AddSegment(SegmentPtr sg); + + /// Fit the vertex position + /// @return pair of (success flag, fitted position) + std::pair FitVertex(); + + /// Update information after fitting + /// @param fit_pos The fitted position + /// @param temp_cluster The cluster being processed + /// @param default_dis_cut Default distance cut + void UpdateInfo(Facade::geo_point_t fit_pos, + Facade::Cluster* temp_cluster, + double default_dis_cut = 4.0*units::cm); + + /// Get segment information at index i + /// @return pair of (segment pointer, index) + std::pair get_seg_info(int i); + + /// Get number of fittable tracks + int get_fittable_tracks(); + + /// Get vertex constraint flag + bool get_flag_vtx_constraint() { return flag_vtx_constraint; } + + /// Set vertex constraint flag + void set_flag_vtx_constraint(bool val) { flag_vtx_constraint = val; } + + /// Set vertex constraint range + void set_vtx_constraint_range(double val) { vtx_constraint_range = val; } + + /// Get the vector of segments + std::vector& get_segments() { return segments; } + + /// Get the vector of point vectors + std::vector>& get_vec_points() { return vec_points; } + + /// Print points for debugging + void print_points(); + + /// Set enforce two track fit flag + void set_enforce_two_track_fit(bool val) { enforce_two_track_fit = val; } + + /// Get enforce two track fit flag + bool get_enforce_two_track_fit() { return enforce_two_track_fit; } + + private: + VertexPtr vtx; + bool enforce_two_track_fit; + bool flag_vtx_constraint; + double vtx_constraint_range; + + double vertex_protect_dis; + double vertex_protect_dis_short_track; + double fit_dis; + + std::vector segments; + std::vector> vec_points; + + // PCA directions: tuple of (dir1, dir2, dir3) + std::vector> vec_PCA_dirs; + + // PCA eigenvalues: tuple of (val1, val2, val3) + std::vector> vec_PCA_vals; + + // Centers for each segment + std::vector vec_centers; + }; + +} // namespace WireCell::Clus::PR + +#endif // WIRECELLCLUS_MYFCN_H diff --git a/clus/inc/WireCellClus/PRSegment.h b/clus/inc/WireCellClus/PRSegment.h index d28990ba..9d944b8d 100644 --- a/clus/inc/WireCellClus/PRSegment.h +++ b/clus/inc/WireCellClus/PRSegment.h @@ -112,6 +112,8 @@ namespace WireCell::Clus::PR { // reset fit properties ... void reset_fit_prop(); + void clear_fit(const IDetectorVolumes::pointer& dv, const std::string& cloud_name="fit"); + int fit_index(int i); void fit_index(int i, int idx); bool fit_flag_skip(int i); @@ -134,6 +136,9 @@ namespace WireCell::Clus::PR { return m_pc_global_indices.find(cloud_name) != m_pc_global_indices.end(); } + bool reset_global_indices(); + bool reset_global_indices(const std::string& cloud_name); + // Add public accessor: int id() const { return m_id; } void set_id(int id) { m_id = id; } diff --git a/clus/src/MyFCN.cxx b/clus/src/MyFCN.cxx new file mode 100644 index 00000000..61c952d9 --- /dev/null +++ b/clus/src/MyFCN.cxx @@ -0,0 +1,283 @@ +#include "WireCellClus/MyFCN.h" +#include "WireCellUtil/Units.h" + +#include +#include + +#include +#include + +using namespace WireCell; +using namespace WireCell::Clus::PR; + +MyFCN::MyFCN(VertexPtr vtx, bool flag_vtx_constraint, double vtx_constraint_range, + double vertex_protect_dis, double vertex_protect_dis_short_track, double fit_dis) + : vtx(vtx) + , enforce_two_track_fit(false) + , flag_vtx_constraint(flag_vtx_constraint) + , vtx_constraint_range(vtx_constraint_range) + , vertex_protect_dis(vertex_protect_dis) + , vertex_protect_dis_short_track(vertex_protect_dis_short_track) + , fit_dis(fit_dis) +{ + segments.clear(); + vec_points.clear(); +} + +MyFCN::~MyFCN() +{ +} + +void MyFCN::print_points() +{ + for (size_t i = 0; i != vec_points.size(); i++) { + for (size_t j = 0; j != vec_points.at(i).size(); j++) { + std::cout << i << " " << j << " " + << vec_points.at(i).at(j).x() / units::cm << " " + << vec_points.at(i).at(j).y() / units::cm << " " + << vec_points.at(i).at(j).z() / units::cm << std::endl; + } + } +} + +void MyFCN::AddSegment(SegmentPtr sg) +{ + // push in ... + segments.push_back(sg); + { + std::vector pts; + vec_points.push_back(pts); + } + + Facade::geo_point_t center(0, 0, 0); + double min_dis = 1e9; + + // Get fit points from segment + const auto& fits = sg->fits(); + if (fits.empty()) { + Facade::geo_point_t a(0, 0, 0); + vec_PCA_dirs.push_back(std::make_tuple(a, a, a)); + vec_PCA_vals.push_back(std::make_tuple(0, 0, 0)); + vec_centers.push_back(a); + return; + } + + std::vector pts; + for (const auto& fit : fits) { + pts.push_back(fit.point); + } + double length = 0; + if (pts.size() > 1) { + auto front = pts.front(); + auto back = pts.back(); + length = std::sqrt(std::pow(front.x() - back.x(), 2) + + std::pow(front.y() - back.y(), 2) + + std::pow(front.z() - back.z(), 2)); + } + + Facade::geo_point_t vtx_pt = vtx->fit().point; + + for (size_t i = 0; i != pts.size(); i++) { + double dis_to_vertex = std::sqrt(std::pow(pts.at(i).x() - vtx_pt.x(), 2) + + std::pow(pts.at(i).y() - vtx_pt.y(), 2) + + std::pow(pts.at(i).z() - vtx_pt.z(), 2)); + if (length > 3.0 * units::cm) { + if (dis_to_vertex < vertex_protect_dis || dis_to_vertex > fit_dis) continue; + } else { + if (dis_to_vertex < vertex_protect_dis_short_track || dis_to_vertex > fit_dis) continue; + } + + vec_points.back().push_back(pts.at(i)); + if (dis_to_vertex < min_dis) { + center = pts.at(i); + min_dis = dis_to_vertex; + } + } + + // calculate the PCA ... + if (vec_points.back().size() > 1) { + int nsum = vec_points.back().size(); + + // Eigen vectors ... + std::vector PCA_axis(3, Facade::geo_point_t(0, 0, 0)); + double PCA_values[3] = {0, 0, 0}; + + Eigen::Matrix3d cov_matrix = Eigen::Matrix3d::Zero(); + + for (size_t k = 0; k != vec_points.back().size(); k++) { + double dx = vec_points.back().at(k).x() - center.x(); + double dy = vec_points.back().at(k).y() - center.y(); + double dz = vec_points.back().at(k).z() - center.z(); + + cov_matrix(0, 0) += dx * dx; + cov_matrix(0, 1) += dx * dy; + cov_matrix(0, 2) += dx * dz; + cov_matrix(1, 1) += dy * dy; + cov_matrix(1, 2) += dy * dz; + cov_matrix(2, 2) += dz * dz; + } + + cov_matrix(1, 0) = cov_matrix(0, 1); + cov_matrix(2, 0) = cov_matrix(0, 2); + cov_matrix(2, 1) = cov_matrix(1, 2); + + cov_matrix /= nsum; + + Eigen::SelfAdjointEigenSolver eigen_solver(cov_matrix); + Eigen::Vector3d eigen_values = eigen_solver.eigenvalues(); + Eigen::Matrix3d eigen_vectors = eigen_solver.eigenvectors(); + + PCA_values[0] = eigen_values(0) + std::pow(0.15 * units::cm, 2); + PCA_values[1] = eigen_values(1) + std::pow(0.15 * units::cm, 2); + PCA_values[2] = eigen_values(2) + std::pow(0.15 * units::cm, 2); + + for (int i = 0; i != 3; i++) { + double norm = std::sqrt(eigen_vectors(0, i) * eigen_vectors(0, i) + + eigen_vectors(1, i) * eigen_vectors(1, i) + + eigen_vectors(2, i) * eigen_vectors(2, i)); + PCA_axis[i] = Facade::geo_point_t(eigen_vectors(0, i) / norm, + eigen_vectors(1, i) / norm, + eigen_vectors(2, i) / norm); + } + + vec_PCA_dirs.push_back(std::make_tuple(PCA_axis[0], PCA_axis[1], PCA_axis[2])); + vec_PCA_vals.push_back(std::make_tuple(PCA_values[0], PCA_values[1], PCA_values[2])); + vec_centers.push_back(center); + + } else { + Facade::geo_point_t a(0, 0, 0); + vec_PCA_dirs.push_back(std::make_tuple(a, a, a)); + vec_PCA_vals.push_back(std::make_tuple(0, 0, 0)); + if (vec_points.back().size() == 1) { + vec_centers.push_back(vec_points.back().back()); + } else { + vec_centers.push_back(a); + } + } +} + +void MyFCN::update_fit_range(double tmp_vertex_protect_dis, double tmp_vertex_protect_dis_short_track, double tmp_fit_dis) +{ + vertex_protect_dis = tmp_vertex_protect_dis; + vertex_protect_dis_short_track = tmp_vertex_protect_dis_short_track; + fit_dis = tmp_fit_dis; + + std::vector tmp_segments = segments; + segments.clear(); + vec_points.clear(); + for (auto it = tmp_segments.begin(); it != tmp_segments.end(); it++) { + AddSegment(*it); + } +} + +int MyFCN::get_fittable_tracks() +{ + int ncount = 0; + for (size_t i = 0; i != vec_points.size(); i++) { + if (vec_points.at(i).size() > 1) ncount++; + } + return ncount; +} + +std::pair MyFCN::get_seg_info(int i) +{ + if (i < (int)segments.size()) { + return std::make_pair(segments.at(i), vec_points.at(i).size()); + } + return std::make_pair(nullptr, 0); +} + +std::pair MyFCN::FitVertex() +{ + Facade::geo_point_t fit_pos = vtx->fit().point; + bool fit_flag = false; + + int ntracks = get_fittable_tracks(); + int npoints = 0; + + int n_large_angles = 0; + for (size_t i = 0; i != vec_PCA_vals.size(); i++) { + Eigen::Vector3d dir1(std::get<0>(vec_PCA_dirs.at(i)).x(), + std::get<0>(vec_PCA_dirs.at(i)).y(), + std::get<0>(vec_PCA_dirs.at(i)).z()); + for (size_t j = i + 1; j < vec_PCA_vals.size(); j++) { + Eigen::Vector3d dir2(std::get<0>(vec_PCA_dirs.at(j)).x(), + std::get<0>(vec_PCA_dirs.at(j)).y(), + std::get<0>(vec_PCA_dirs.at(j)).z()); + double angle = std::acos(dir1.dot(dir2)) * 180.0 / M_PI; + if (angle > 15) n_large_angles++; + } + } + + if ((ntracks > 2 && n_large_angles > 1) || (ntracks >= 2 && enforce_two_track_fit && n_large_angles >= 1)) { + + // start the fit ... + Eigen::VectorXd temp_pos_3D_init(3), temp_pos_3D(3); // to be fitted + temp_pos_3D_init(0) = fit_pos.x(); + temp_pos_3D_init(1) = fit_pos.y(); + temp_pos_3D_init(2) = fit_pos.z(); + + Eigen::Vector3d b(0, 0, 0); + Eigen::MatrixXd A = Eigen::MatrixXd::Zero(3, 3); + + for (size_t i = 0; i != vec_PCA_vals.size(); i++) { + if (std::get<0>(vec_PCA_vals.at(i)) > 0) { + npoints += vec_points.at(i).size(); + + // fill the matrix ... first row, second column + Eigen::MatrixXd R(3, 3); + R(0, 0) = 0; R(0, 1) = 0; R(0, 2) = 0; + double val1 = std::sqrt(std::get<0>(vec_PCA_vals.at(i)) / std::get<1>(vec_PCA_vals.at(i))); + R(1, 0) = val1 * std::get<1>(vec_PCA_dirs.at(i)).x(); + R(1, 1) = val1 * std::get<1>(vec_PCA_dirs.at(i)).y(); + R(1, 2) = val1 * std::get<1>(vec_PCA_dirs.at(i)).z(); + val1 = std::sqrt(std::get<0>(vec_PCA_vals.at(i)) / std::get<2>(vec_PCA_vals.at(i))); + R(2, 0) = val1 * std::get<2>(vec_PCA_dirs.at(i)).x(); + R(2, 1) = val1 * std::get<2>(vec_PCA_dirs.at(i)).y(); + R(2, 2) = val1 * std::get<2>(vec_PCA_dirs.at(i)).z(); + + Eigen::Vector3d data(vec_centers.at(i).x(), vec_centers.at(i).y(), vec_centers.at(i).z()); + data = R * data; + Eigen::MatrixXd RT = R.transpose(); + + b += RT * data; + A += RT * R; + } + } + + // add constraint ... + if (flag_vtx_constraint) { + Eigen::MatrixXd R = Eigen::MatrixXd::Zero(3, 3); + R(0, 0) = 1.0 / vtx_constraint_range * std::sqrt(npoints); + R(1, 1) = 1.0 / vtx_constraint_range * std::sqrt(npoints); + R(2, 2) = 1.0 / vtx_constraint_range * std::sqrt(npoints); + + Eigen::Vector3d data(fit_pos.x() / vtx_constraint_range * std::sqrt(npoints), + fit_pos.y() / vtx_constraint_range * std::sqrt(npoints), + fit_pos.z() / vtx_constraint_range * std::sqrt(npoints)); + Eigen::MatrixXd RT = R.transpose(); + b += RT * data; + A += RT * R; + } + + Eigen::BiCGSTAB solver; + solver.compute(A); + temp_pos_3D = solver.solveWithGuess(b, temp_pos_3D_init); + + if (!std::isnan(solver.error())) { + fit_pos = Facade::geo_point_t(temp_pos_3D(0), temp_pos_3D(1), temp_pos_3D(2)); + fit_flag = true; + } else { + std::cout << "Cluster: "; + if (vtx->cluster()) { + std::cout << vtx->cluster()->get_cluster_id(); + } else { + std::cout << "unknown"; + } + std::cout << " Fit Vertex Failed!" << std::endl; + } + } + + return std::make_pair(fit_flag, fit_pos); +} + diff --git a/clus/src/PRSegment.cxx b/clus/src/PRSegment.cxx index 192d4ff5..cdd6526d 100644 --- a/clus/src/PRSegment.cxx +++ b/clus/src/PRSegment.cxx @@ -60,6 +60,60 @@ namespace WireCell::Clus::PR { } + bool Segment::reset_global_indices(){ + if (m_pc_global_indices.empty()) { + return false; + } + m_pc_global_indices.clear(); + return true; + } + + bool Segment::reset_global_indices(const std::string& cloud_name){ + auto it = m_pc_global_indices.find(cloud_name); + if (it == m_pc_global_indices.end()) { + return false; + } + m_pc_global_indices.erase(it); + return true; + } + + void Segment::clear_fit(const IDetectorVolumes::pointer& dv, const std::string& cloud_name){ + // Unset the kFit flag + unset_flags(SegmentFlags::kFit); + + // Reset fit vector to match wcpts size and copy point data + m_fits.clear(); + m_fits.resize(m_wcpts.size()); + for (size_t i = 0; i != m_wcpts.size(); i++) { + m_fits.at(i).point = m_wcpts.at(i).point; + // Reset other Fit fields to default values + m_fits.at(i).dQ = -1; + m_fits.at(i).dx = 0; + m_fits.at(i).pu = -1; + m_fits.at(i).pv = -1; + m_fits.at(i).pw = -1; + m_fits.at(i).pt = 0; + m_fits.at(i).reduced_chi2 = -1; + m_fits.at(i).index = -1; + m_fits.at(i).range = -1; + m_fits.at(i).flag_fix = false; + } + + // Reset direction and particle information + m_dirsign = 0; + m_dir_weak = false; + m_particle_score = 100; + m_particle_info = nullptr; + + // Recreate the dynamic point cloud for fit points + if (dv) { + create_segment_fit_point_cloud(shared_from_this(), dv, cloud_name); + } + + reset_global_indices(); + } + + From 057db7c30fc3ff6455bbb1cd8f1a0d6d66f0e0c6 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Fri, 2 Jan 2026 08:58:05 -0800 Subject: [PATCH 080/111] fully implement MyFCN --- clus/inc/WireCellClus/MyFCN.h | 5 +- clus/inc/WireCellClus/TrackFitting.h | 12 ++ clus/src/MyFCN.cxx | 190 ++++++++++++++++++++++++++- 3 files changed, 205 insertions(+), 2 deletions(-) diff --git a/clus/inc/WireCellClus/MyFCN.h b/clus/inc/WireCellClus/MyFCN.h index c06facd8..c45479b3 100644 --- a/clus/inc/WireCellClus/MyFCN.h +++ b/clus/inc/WireCellClus/MyFCN.h @@ -4,6 +4,8 @@ #include "WireCellClus/PRGraph.h" #include "WireCellClus/Facade_Cluster.h" #include "WireCellUtil/Units.h" +#include "WireCellClus/TrackFitting.h" + #include #include @@ -47,7 +49,8 @@ namespace WireCell::Clus::PR { /// @param temp_cluster The cluster being processed /// @param default_dis_cut Default distance cut void UpdateInfo(Facade::geo_point_t fit_pos, - Facade::Cluster* temp_cluster, + Facade::Cluster& temp_cluster, + TrackFitting& track_fitter, IDetectorVolumes::pointer dv, double default_dis_cut = 4.0*units::cm); /// Get segment information at index i diff --git a/clus/inc/WireCellClus/TrackFitting.h b/clus/inc/WireCellClus/TrackFitting.h index c25ea6c3..49fd9706 100644 --- a/clus/inc/WireCellClus/TrackFitting.h +++ b/clus/inc/WireCellClus/TrackFitting.h @@ -430,6 +430,18 @@ namespace WireCell::Clus { std::vector> get_paf() const {return paf;} std::vector get_reduced_chi2() const { return reduced_chi2; } + /** + * Get geometry information for wire plane offsets + * @return Map of WirePlaneId to tuple (offset_t, offset_u, offset_v, offset_w) + */ + const std::map>& get_wpid_offsets() const { return wpid_offsets; } + + /** + * Get geometry information for wire plane slopes + * @return Map of WirePlaneId to tuple (slope_t, slope_yu_zu, slope_yv_zv, slope_yw_zw) + */ + const std::map, std::pair, std::pair>>& get_wpid_slopes() const { return wpid_slopes; } + private: // Core parameters - centralized storage Parameters m_params; diff --git a/clus/src/MyFCN.cxx b/clus/src/MyFCN.cxx index 61c952d9..95f85b2a 100644 --- a/clus/src/MyFCN.cxx +++ b/clus/src/MyFCN.cxx @@ -3,7 +3,6 @@ #include #include - #include #include @@ -281,3 +280,192 @@ std::pair MyFCN::FitVertex() return std::make_pair(fit_flag, fit_pos); } +void MyFCN::UpdateInfo(Facade::geo_point_t fit_pos, Facade::Cluster& temp_cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, double default_dis_cut){ + + // Get PC transform and cluster parameters + auto pcts = track_fitter.get_pc_transforms(); + const auto transform = pcts->pc_transform(temp_cluster.get_scope_transform(temp_cluster.get_default_scope())); + double cluster_t0 = temp_cluster.get_cluster_t0(); + + // Get APA/face for the fit position + auto test_wpid = dv->contained_by(fit_pos); + if (test_wpid.apa() == -1 || test_wpid.face() == -1) { + std::cout << "Warning: fit_pos not contained in detector volume" << std::endl; + return; + } + int apa = test_wpid.apa(); + int face = test_wpid.face(); + + // Get geometry parameters from TrackFitting + const auto& wpid_offsets = track_fitter.get_wpid_offsets(); + const auto& wpid_slopes = track_fitter.get_wpid_slopes(); + + WirePlaneId wpid(kAllLayers, face, apa); + auto offset_it = wpid_offsets.find(wpid); + auto slope_it = wpid_slopes.find(wpid); + + if (offset_it == wpid_offsets.end() || slope_it == wpid_slopes.end()) { + std::cout << "Warning: geometry parameters not found for APA " << apa << " Face " << face << std::endl; + return; + } + + auto offset_t = std::get<0>(offset_it->second); + auto offset_u = std::get<1>(offset_it->second); + auto offset_v = std::get<2>(offset_it->second); + auto offset_w = std::get<3>(offset_it->second); + auto slope_x = std::get<0>(slope_it->second); + auto slope_yu = std::get<1>(slope_it->second).first; + auto slope_zu = std::get<1>(slope_it->second).second; + auto slope_yv = std::get<2>(slope_it->second).first; + auto slope_zv = std::get<2>(slope_it->second).second; + auto slope_yw = std::get<3>(slope_it->second).first; + auto slope_zw = std::get<3>(slope_it->second).second; + + // Transform to raw coordinates for wire plane calculations + auto fit_pos_raw = transform->backward(fit_pos, cluster_t0, face, apa); + auto vtx_pos_raw = transform->backward(vtx->fit().point, cluster_t0, face, apa); + + // Print update information if vertex moved significantly + if ((fit_pos - vtx->fit().point).magnitude() > 0.01 * units::cm) { + std::cout << "Cluster: "; + if (vtx->cluster()) { + std::cout << vtx->cluster()->get_cluster_id(); + } else { + std::cout << "unknown"; + } + std::cout << " Update Vertex: (" + << offset_u + (slope_yu * fit_pos_raw.y() + slope_zu * fit_pos_raw.z()) << ", " + << offset_v + (slope_yv * fit_pos_raw.y() + slope_zv * fit_pos_raw.z()) << ", " + << offset_w + (slope_yw * fit_pos_raw.y() + slope_zw * fit_pos_raw.z()) << ", " + << offset_t + slope_x * fit_pos_raw.x() << ") <- (" + << offset_u + (slope_yu * vtx_pos_raw.y() + slope_zu * vtx_pos_raw.z()) << ", " + << offset_v + (slope_yv * vtx_pos_raw.y() + slope_zv * vtx_pos_raw.z()) << ", " + << offset_w + (slope_yw * vtx_pos_raw.y() + slope_zw * vtx_pos_raw.z()) << ", " + << offset_t + slope_x * vtx_pos_raw.x() << ")" << std::endl; + } + + // Get steiner point cloud from cluster + if (!temp_cluster.has_pc("steiner_pc") || temp_cluster.get_pc("steiner_pc").size() == 0) { + std::cout << "Warning: steiner_pc not found in cluster" << std::endl; + return; + } + const auto& steiner_pc = temp_cluster.get_pc("steiner_pc"); + const auto& coords = temp_cluster.get_default_scope().coords; + const auto& x_coords = steiner_pc.get(coords.at(0))->elements(); + const auto& y_coords = steiner_pc.get(coords.at(1))->elements(); + const auto& z_coords = steiner_pc.get(coords.at(2))->elements(); + + // Find closest steiner point to new fit position + auto vtx_knn_results = temp_cluster.kd_steiner_knn(1, fit_pos, "steiner_pc"); + size_t vtx_new_idx = vtx_knn_results[0].first; + Facade::geo_point_t vtx_new_pt(x_coords[vtx_new_idx], y_coords[vtx_new_idx], z_coords[vtx_new_idx]); + + // Update each segment connected to this vertex + for (size_t i = 0; i != segments.size(); i++) { + auto& seg_wcpts = segments.at(i)->wcpts(); + if (seg_wcpts.empty()) continue; + + // Determine if vertex is at front or back of segment + bool flag_front = false; + double dis_front = (seg_wcpts.front().point - vtx->fit().point).magnitude(); + double dis_back = (seg_wcpts.back().point - vtx->fit().point).magnitude(); + flag_front = (dis_front < dis_back); + + // Find closest wcpt to the PCA center, excluding points too close to vertex + double max_dis = std::max(dis_front, dis_back); + double dis_cut = (max_dis > 2 * default_dis_cut) ? default_dis_cut : 0; + + size_t min_idx = 0; + double min_dis = 1e9; + for (size_t j = 0; j != seg_wcpts.size(); j++) { + double dis_to_center = (seg_wcpts.at(j).point - vec_centers.at(i)).magnitude(); + double dis_to_vtx = (seg_wcpts.at(j).point - vtx->fit().point).magnitude(); + if (dis_to_center < min_dis && dis_to_vtx > dis_cut) { + min_idx = j; + min_dis = dis_to_center; + } + } + + auto& min_wcp = seg_wcpts.at(min_idx); + + // Create new path from new vertex position to closest point using steiner graph + std::list new_list; + new_list.push_back(WCPoint{vtx_new_pt}); + + // Interpolate intermediate points + double dis_step = 2.0 * units::cm; + double total_dis = (vtx_new_pt - min_wcp.point).magnitude(); + int ncount = std::round(total_dis / dis_step); + if (ncount < 2) ncount = 2; + + // double cumulative_ray_length = 0.0; + for (int qx = 1; qx < ncount; qx++) { + Facade::geo_point_t tmp_p( + vtx_new_pt.x() + (min_wcp.point.x() - vtx_new_pt.x()) / ncount * qx, + vtx_new_pt.y() + (min_wcp.point.y() - vtx_new_pt.y()) / ncount * qx, + vtx_new_pt.z() + (min_wcp.point.z() - vtx_new_pt.z()) / ncount * qx + ); + auto tmp_knn_results = temp_cluster.kd_steiner_knn(1, tmp_p, "steiner_pc"); + size_t tmp_idx = tmp_knn_results[0].first; + Facade::geo_point_t tmp_pt(x_coords[tmp_idx], y_coords[tmp_idx], z_coords[tmp_idx]); + + // Skip if too far from target point or duplicate + if ((tmp_pt - tmp_p).magnitude() > 0.3 * units::cm) continue; + double dis_to_last = (tmp_pt - new_list.back().point).magnitude(); + if (dis_to_last > 0.01 * units::cm && (tmp_pt - min_wcp.point).magnitude() > 0.01 * units::cm) { + // cumulative_ray_length += dis_to_last; + new_list.push_back(WCPoint{tmp_pt}); + } + } + // cumulative_ray_length += (min_wcp.point - new_list.back().point).magnitude(); + new_list.push_back(WCPoint{min_wcp.point}); + + // Replace segment path + std::list old_list(seg_wcpts.begin(), seg_wcpts.end()); + + if (flag_front) { + // Remove old path from front to min_wcp + while (!old_list.empty() && (old_list.front().point - min_wcp.point).magnitude() > 0.01 * units::cm) { + old_list.pop_front(); + } + if (!old_list.empty()) old_list.pop_front(); + + // Prepend new path + for (auto it = new_list.rbegin(); it != new_list.rend(); ++it) { + old_list.push_front(*it); + } + } else { + // Remove old path from back to min_wcp + while (!old_list.empty() && (old_list.back().point - min_wcp.point).magnitude() > 0.01 * units::cm) { + old_list.pop_back(); + } + if (!old_list.empty()) old_list.pop_back(); + + // Append new path + for (auto it = new_list.rbegin(); it != new_list.rend(); ++it) { + old_list.push_back(*it); + } + } + + // Update segment wcpts + seg_wcpts.clear(); + seg_wcpts.reserve(old_list.size()); + std::copy(old_list.begin(), old_list.end(), std::back_inserter(seg_wcpts)); + + // Clear fit data - will need to be recalculated + segments.at(i)->clear_fit(dv); + } + + // Update vertex with new fit position and wire coordinates + auto& vtx_fit = vtx->fit(); + vtx_fit.point = fit_pos; + vtx_fit.pu = offset_u + (slope_yu * fit_pos_raw.y() + slope_zu * fit_pos_raw.z()); + vtx_fit.pv = offset_v + (slope_yv * fit_pos_raw.y() + slope_zv * fit_pos_raw.z()); + vtx_fit.pw = offset_w + (slope_yw * fit_pos_raw.y() + slope_zw * fit_pos_raw.z()); + vtx_fit.pt = offset_t + slope_x * fit_pos_raw.x(); + vtx_fit.paf = std::make_pair(apa, face); + vtx_fit.flag_fix = true; + + // Update vertex wcpt + vtx->wcpt().point = vtx_new_pt; +} \ No newline at end of file From 6b66163eadd646f4478c02942f21f11204d1f46d Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Fri, 2 Jan 2026 09:08:21 -0800 Subject: [PATCH 081/111] implement the fit_vertex function --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 1 + clus/src/NeutrinoVertexFinder.cxx | 66 +++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index d888d3a0..5d8a7f41 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -110,6 +110,7 @@ namespace WireCell::Clus::PR { std::pair find_cont_muon_segment(Graph &graph, SegmentPtr sg, VertexPtr vtx, bool flag_ignore_dQ_dx = false); bool examine_direction(Graph& graph, VertexPtr vertex, VertexPtr main_vertex, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model, bool flag_final = false); + bool fit_vertex(Facade::Cluster& cluster, VertexPtr vertex, VertexPtr main_vertex, std::set& sg_set, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); // global information transfer void transfer_info_from_segment_to_cluster(Graph& graph, Facade::Cluster& cluster, const std::string& cloud_name = "associated_points"); diff --git a/clus/src/NeutrinoVertexFinder.cxx b/clus/src/NeutrinoVertexFinder.cxx index a62d2af7..268aaf63 100644 --- a/clus/src/NeutrinoVertexFinder.cxx +++ b/clus/src/NeutrinoVertexFinder.cxx @@ -1,6 +1,7 @@ #include "WireCellClus/NeutrinoPatternBase.h" #include "WireCellClus/PRSegmentFunctions.h" #include "WireCellClus/FiducialUtils.h" +#include "WireCellClus/MyFCN.h" using namespace WireCell::Clus::PR; using namespace WireCell::Clus; @@ -1709,3 +1710,68 @@ bool PatternAlgorithms::eliminate_short_vertex_activities(Graph& graph, Facade:: return flag_updated; } + + +bool PatternAlgorithms::fit_vertex(Facade::Cluster& cluster, VertexPtr vertex, VertexPtr main_vertex, std::set& sg_set, TrackFitting& track_fitter, IDetectorVolumes::pointer dv){ + // Allow to move 1.5 cm - create MyFCN object with constraint parameters + MyFCN fcn(vertex, true, 0.43*units::cm, 1.5*units::cm, 0.9*units::cm, 6*units::cm); + + // Add all segments to the fitting + for (auto it = sg_set.begin(); it != sg_set.end(); it++) { + fcn.AddSegment(*it); + } + + // If this is the main vertex, enforce two track fit + if (vertex == main_vertex) fcn.set_enforce_two_track_fit(true); + + // Perform vertex fitting + std::pair results = fcn.FitVertex(); + + // Get grouping for charge calculation + auto grouping = cluster.grouping(); + if (!grouping) { + if (results.first) + fcn.UpdateInfo(results.second, cluster, track_fitter, dv); + return results.first; + } + + // Get transform for coordinate conversion + const auto transform = track_fitter.get_pc_transforms()->pc_transform( + cluster.get_scope_transform(cluster.get_default_scope())); + // double cluster_t0 = cluster.get_cluster_t0(); + + // Get old and new vertex positions + Facade::geo_point_t old_pos = vertex->fit().point; + Facade::geo_point_t new_pos = results.second; + + // Get APA/face for old and new positions + auto old_wpid = dv->contained_by(old_pos); + auto new_wpid = dv->contained_by(new_pos); + + // Calculate average charge at old and new positions + double old_charge = 0; + double new_charge = 0; + + if (old_wpid.apa() != -1 && old_wpid.face() != -1) { + old_charge = grouping->get_ave_3d_charge(old_pos, old_wpid.apa(), old_wpid.face(), 0.6*units::cm); + } + + if (new_wpid.apa() != -1 && new_wpid.face() != -1) { + new_charge = grouping->get_ave_3d_charge(new_pos, new_wpid.apa(), new_wpid.face(), 0.6*units::cm); + } + + // Check charge conditions - if new position has much lower charge, keep old position + if (new_charge < 5000 && new_charge < 0.4*old_charge) { + results.second = old_pos; + } else if (new_charge < 8000 && new_charge < 0.6*old_charge) { + // Reduce the strength - keep old position + results.second = old_pos; + new_charge = old_charge; + } + + // Update vertex and segment information with fitted position + if (results.first) + fcn.UpdateInfo(results.second, cluster, track_fitter, dv); + + return results.first; +} From 38be7a01e0cc587e98fc3a7a4ac61327e2286e81 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Fri, 2 Jan 2026 15:02:25 -0800 Subject: [PATCH 082/111] implement the improve_vertex function --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 1 + clus/src/NeutrinoVertexFinder.cxx | 421 ++++++++++++++++++++ 2 files changed, 422 insertions(+) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 5d8a7f41..1ab0518b 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -111,6 +111,7 @@ namespace WireCell::Clus::PR { bool examine_direction(Graph& graph, VertexPtr vertex, VertexPtr main_vertex, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model, bool flag_final = false); bool fit_vertex(Facade::Cluster& cluster, VertexPtr vertex, VertexPtr main_vertex, std::set& sg_set, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); + void improve_vertex(Graph& graph, Facade::Cluster& cluster, VertexPtr main_vertex, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model, bool flag_search_vertex_activity = true , bool flag_final_vertex = false); // global information transfer void transfer_info_from_segment_to_cluster(Graph& graph, Facade::Cluster& cluster, const std::string& cloud_name = "associated_points"); diff --git a/clus/src/NeutrinoVertexFinder.cxx b/clus/src/NeutrinoVertexFinder.cxx index 268aaf63..e38fb783 100644 --- a/clus/src/NeutrinoVertexFinder.cxx +++ b/clus/src/NeutrinoVertexFinder.cxx @@ -1775,3 +1775,424 @@ bool PatternAlgorithms::fit_vertex(Facade::Cluster& cluster, VertexPtr vertex, V return results.first; } + + +void PatternAlgorithms::improve_vertex(Graph& graph, Facade::Cluster& cluster, VertexPtr main_vertex, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model, bool flag_search_vertex_activity, bool flag_final_vertex){ + + std::set fitted_vertices; + std::set existing_segments; + + // Check if all segments are showers, no need to fit vertex with only two legs + bool flag_skip_two_legs = false; + { + int ntracks = 0; + for (auto it = boost::edges(graph).first; it != boost::edges(graph).second; ++it) { + SegmentPtr sg = graph[*it].segment; + if (!sg || sg->cluster() != &cluster) continue; + existing_segments.insert(sg); + bool is_shower = sg->flags_any(SegmentFlags::kShowerTrajectory) || sg->flags_any(SegmentFlags::kShowerTopology); + if (!is_shower) ntracks++; + } + if (ntracks == 0) + flag_skip_two_legs = true; // all showers + } + + bool flag_found_vertex_activities = false; + + // Search for vertex activities + if (flag_search_vertex_activity) { + if (examine_structure_4(main_vertex, flag_final_vertex, graph, cluster, track_fitter, dv)) { + flag_found_vertex_activities = true; + track_fitter.do_multi_tracking(true, true, true); + } + } + + bool flag_update_fit = false; + + // Find and fit vertices + for (auto v : boost::make_iterator_range(boost::vertices(graph))) { + VertexPtr vtx = graph[v].vertex; + if (!vtx || vtx->cluster() != &cluster) continue; + + // Get segments connected to this vertex + std::set vertex_segments; + for (auto it = boost::out_edges(v, graph).first; it != boost::out_edges(v, graph).second; ++it) { + SegmentPtr sg = graph[*it].segment; + if (sg && sg->cluster() == &cluster) { + vertex_segments.insert(sg); + } + } + + if (vertex_segments.size() <= 2 && vtx != main_vertex) continue; + + int ntracks = 0, nshowers = 0; + int n_long_muons = 0; + for (auto sg : vertex_segments) { + bool is_shower = sg->flags_any(SegmentFlags::kShowerTrajectory) || sg->flags_any(SegmentFlags::kShowerTopology); + if (is_shower) nshowers++; + else ntracks++; + if (segments_in_long_muon.find(sg) != segments_in_long_muon.end()) n_long_muons++; + } + + if (ntracks == 0 && vtx != main_vertex) continue; + if (flag_skip_two_legs && vertex_segments.size() <= 2) continue; + + auto wcp_save = vtx->wcpt(); + + bool flag_update = fit_vertex(cluster, vtx, main_vertex, vertex_segments, track_fitter, dv); + if (flag_update) fitted_vertices.insert(vtx); + if (flag_update) { + flag_update_fit = true; + + double tmp_dis = std::sqrt(std::pow(wcp_save.point.x() - vtx->wcpt().point.x(), 2) + + std::pow(wcp_save.point.y() - vtx->wcpt().point.y(), 2) + + std::pow(wcp_save.point.z() - vtx->wcpt().point.z(), 2)); + + if (tmp_dis > 0.5*units::cm) { // if the vertex moved far, refit + track_fitter.do_multi_tracking(true, true, true); + fit_vertex(cluster, vtx, main_vertex, vertex_segments, track_fitter, dv); + } + } + + (void)nshowers; + (void)n_long_muons; + } + + if (flag_update_fit) { + // Do the overall fit again + track_fitter.do_multi_tracking(true, true, true); + + bool flag_keep_main_vertex = false; + Facade::geo_point_t main_vtx_pt; + if (main_vertex != nullptr) { + main_vtx_pt = main_vertex->fit().point; + flag_keep_main_vertex = true; + } + + examine_vertices(graph, cluster, track_fitter, dv); + + if (flag_keep_main_vertex) { + // Check if main_vertex still exists in graph + bool found_main_vertex = false; + for (auto v : boost::make_iterator_range(boost::vertices(graph))) { + if (graph[v].vertex == main_vertex) { + found_main_vertex = true; + break; + } + } + + if (!found_main_vertex) { + double min_dis = 1e9; + for (auto v : boost::make_iterator_range(boost::vertices(graph))) { + VertexPtr vtx = graph[v].vertex; + if (!vtx || vtx->cluster() != &cluster) continue; + double dis = std::sqrt(std::pow(vtx->fit().point.x() - main_vtx_pt.x(), 2) + + std::pow(vtx->fit().point.y() - main_vtx_pt.y(), 2) + + std::pow(vtx->fit().point.z() - main_vtx_pt.z(), 2)); + if (dis < min_dis) { + min_dis = dis; + main_vertex = vtx; + } + } + } + } + } + + std::vector refit_vertices; + flag_update_fit = false; + + if (flag_search_vertex_activity) { + // Search for vertex activities again + if (!flag_found_vertex_activities) { + if (examine_structure_4(main_vertex, flag_final_vertex, graph, cluster, track_fitter, dv)) { + flag_found_vertex_activities = true; + + // Get segments connected to main_vertex + std::set main_vertex_segments; + for (auto v : boost::make_iterator_range(boost::vertices(graph))) { + if (graph[v].vertex == main_vertex) { + for (auto it = boost::out_edges(v, graph).first; it != boost::out_edges(v, graph).second; ++it) { + SegmentPtr sg = graph[*it].segment; + if (sg && sg->cluster() == &cluster) { + main_vertex_segments.insert(sg); + } + } + break; + } + } + + if (main_vertex_segments.size() == 3) refit_vertices.push_back(main_vertex); + flag_update_fit = true; + } + } + + for (auto v : boost::make_iterator_range(boost::vertices(graph))) { + VertexPtr vtx = graph[v].vertex; + if (!vtx || vtx->cluster() != &cluster) continue; + + // Get segments connected to this vertex + std::set vertex_segments; + for (auto it = boost::out_edges(v, graph).first; it != boost::out_edges(v, graph).second; ++it) { + SegmentPtr sg = graph[*it].segment; + if (sg && sg->cluster() == &cluster) { + vertex_segments.insert(sg); + } + } + + if (vertex_segments.size() <= 2 && vtx != main_vertex) continue; + + int ntracks = 0, nshowers = 0; + for (auto sg : vertex_segments) { + bool is_shower = sg->flags_any(SegmentFlags::kShowerTrajectory) || sg->flags_any(SegmentFlags::kShowerTopology); + if (is_shower) nshowers++; + else ntracks++; + } + + if (ntracks == 0 && vtx != main_vertex) continue; + if (vertices_in_long_muon.find(vtx) != vertices_in_long_muon.end()) continue; + if (vtx == main_vertex && flag_found_vertex_activities) continue; + if (flag_skip_two_legs && vertex_segments.size() <= 2) continue; + + double search_range = 1.5*units::cm; + if (vertex_segments.size() == 1) search_range = 3.0*units::cm; + + bool flag_update = search_for_vertex_activities(graph, vtx, vertex_segments, cluster, track_fitter, dv, search_range); + + if (flag_update) { + // Get updated segments + vertex_segments.clear(); + for (auto it = boost::out_edges(v, graph).first; it != boost::out_edges(v, graph).second; ++it) { + SegmentPtr sg = graph[*it].segment; + if (sg && sg->cluster() == &cluster) { + vertex_segments.insert(sg); + } + } + if (vertex_segments.size() == 3) refit_vertices.push_back(vtx); + flag_update_fit = true; + } + (void)nshowers; + } + + if (flag_update_fit) { + // Do the overall fit again + track_fitter.do_multi_tracking(true, true, true); + flag_update_fit = false; + + // Redo the fit + for (auto vtx : refit_vertices) { + std::set vertex_segments; + for (auto v : boost::make_iterator_range(boost::vertices(graph))) { + if (graph[v].vertex == vtx) { + for (auto it = boost::out_edges(v, graph).first; it != boost::out_edges(v, graph).second; ++it) { + SegmentPtr sg = graph[*it].segment; + if (sg && sg->cluster() == &cluster) { + vertex_segments.insert(sg); + } + } + break; + } + } + + bool flag_update = fit_vertex(cluster, vtx, main_vertex, vertex_segments, track_fitter, dv); + if (flag_update) fitted_vertices.insert(vtx); + if (flag_update) flag_update_fit = true; + } + if (flag_update_fit) track_fitter.do_multi_tracking(true, true, true); + } + + // Eliminate short tracks + if (eliminate_short_vertex_activities(graph, cluster, main_vertex, existing_segments, track_fitter, dv)) { + track_fitter.do_multi_tracking(true, true, true); + } + + // Determine directions for segments + for (auto it = boost::edges(graph).first; it != boost::edges(graph).second; ++it) { + SegmentPtr sg1 = graph[*it].segment; + if (!sg1 || sg1->cluster() != &cluster) continue; + + if (!sg1->particle_info()) { + segment_is_shower_topology(sg1); + + VertexPtr start_v = nullptr, end_v = nullptr; + auto source_v = boost::source(*it, graph); + auto target_v = boost::target(*it, graph); + + auto& wcpts = sg1->wcpts(); + if (!wcpts.empty()) { + if ((graph[source_v].vertex->wcpt().point - wcpts.front().point).magnitude() < 0.01*units::cm) { + start_v = graph[source_v].vertex; + end_v = graph[target_v].vertex; + } else { + end_v = graph[source_v].vertex; + start_v = graph[target_v].vertex; + } + } + + if (start_v && end_v) { + int start_n = boost::out_degree(source_v, graph); + int end_n = boost::out_degree(target_v, graph); + + if (segment_is_shower_trajectory(sg1)) { + // Trajectory shower + segment_determine_shower_direction_trajectory(sg1, start_n, end_n, particle_data, recomb_model, 43000/units::cm, false); + } else { + segment_determine_dir_track(sg1, start_n, end_n, particle_data, recomb_model, 43000/units::cm, false); + } + } + } + } + } else { // flag_search_vertex_activity + for (auto vtx : fitted_vertices) { + // Find vertex descriptor + for (auto v : boost::make_iterator_range(boost::vertices(graph))) { + if (graph[v].vertex != vtx) continue; + + for (auto it = boost::out_edges(v, graph).first; it != boost::out_edges(v, graph).second; ++it) { + SegmentPtr sg = graph[*it].segment; + if (!sg || sg->cluster() != &cluster) continue; + + if (!sg->particle_info()) segment_is_shower_topology(sg); + + VertexPtr start_v = nullptr, end_v = nullptr; + auto source_v = boost::source(*it, graph); + auto target_v = boost::target(*it, graph); + + auto& wcpts = sg->wcpts(); + if (!wcpts.empty()) { + if ((graph[source_v].vertex->wcpt().point - wcpts.front().point).magnitude() < 0.01*units::cm) { + start_v = graph[source_v].vertex; + end_v = graph[target_v].vertex; + } else { + end_v = graph[source_v].vertex; + start_v = graph[target_v].vertex; + } + } + + bool flag_print = false; + bool is_shower = sg->flags_any(SegmentFlags::kShowerTrajectory) || sg->flags_any(SegmentFlags::kShowerTopology); + if (start_v && end_v && !is_shower) { + // Track + int start_n = boost::out_degree(source_v, graph); + int end_n = boost::out_degree(target_v, graph); + segment_determine_dir_track(sg, start_n, end_n, particle_data, recomb_model, 43000/units::cm, flag_print); + } + } + } + } + } + + // Handle special cases for main_vertex segments + if (main_vertex != nullptr && main_vertex->cluster() == &cluster) { + // Find main_vertex descriptor + for (auto v : boost::make_iterator_range(boost::vertices(graph))) { + if (graph[v].vertex != main_vertex) continue; + + for (auto it = boost::out_edges(v, graph).first; it != boost::out_edges(v, graph).second; ++it) { + SegmentPtr sg = graph[*it].segment; + if (!sg || sg->cluster() != &cluster) continue; + + std::pair pair_result = calculate_num_daughter_showers(graph, main_vertex, sg, false); + + double medium_dQdx = segment_median_dQ_dx(sg); + if ((pair_result.first <= 2 || (medium_dQdx/(43e3/units::cm) > 1.6 && pair_result.first <= 3)) && segment_is_shower_trajectory(sg)) { + if (!segment_is_shower_trajectory(sg, 1.0*units::cm, 43000/units::cm)) { + VertexPtr start_v = nullptr, end_v = nullptr; + auto source_v = boost::source(*it, graph); + auto target_v = boost::target(*it, graph); + + auto& wcpts = sg->wcpts(); + if (!wcpts.empty()) { + if ((graph[source_v].vertex->wcpt().point - wcpts.front().point).magnitude() < 0.01*units::cm) { + start_v = graph[source_v].vertex; + end_v = graph[target_v].vertex; + } else { + end_v = graph[source_v].vertex; + start_v = graph[target_v].vertex; + } + } + + if (start_v && end_v) { + int start_n = boost::out_degree(source_v, graph); + int end_n = boost::out_degree(target_v, graph); + segment_determine_dir_track(sg, start_n, end_n, particle_data, recomb_model, 43000/units::cm, false); + } + } + } + + // Examine topology case + if (pair_result.first == 1 && segment_is_shower_topology(sg, false)) { + int dir_save = sg->dirsign(); + + VertexPtr start_v = nullptr, end_v = nullptr; + auto source_v = boost::source(*it, graph); + auto target_v = boost::target(*it, graph); + + auto& wcpts = sg->wcpts(); + if (!wcpts.empty()) { + if ((graph[source_v].vertex->wcpt().point - wcpts.front().point).magnitude() < 0.01*units::cm) { + start_v = graph[source_v].vertex; + end_v = graph[target_v].vertex; + } else { + end_v = graph[source_v].vertex; + start_v = graph[target_v].vertex; + } + } + + if (start_v && end_v) { + sg->unset_flags(SegmentFlags::kShowerTopology); + int start_n = boost::out_degree(source_v, graph); + int end_n = boost::out_degree(target_v, graph); + segment_determine_dir_track(sg, start_n, end_n, particle_data, recomb_model, 43000/units::cm, false); + + if ((sg->particle_info() && sg->particle_info()->pdg() == 2212 && sg->particle_score() < 0.09) || + (sg->particle_info() && sg->particle_info()->pdg() == 13 && sg->particle_score() < 0.06)) { + sg->unset_flags(SegmentFlags::kShowerTopology); + } else { + sg->set_flags(SegmentFlags::kShowerTopology); + if (!sg->particle_info()) { + sg->particle_info() = std::make_shared(); + } + sg->particle_info()->set_pdg(11); + sg->particle_score(100); + sg->dirsign(dir_save); + sg->particle_info()->set_mass(particle_data->get_particle_mass(11)); + } + } + } + + if (flag_skip_two_legs && existing_segments.find(sg) == existing_segments.end()) { + VertexPtr start_v = nullptr, end_v = nullptr; + auto source_v = boost::source(*it, graph); + auto target_v = boost::target(*it, graph); + + auto& wcpts = sg->wcpts(); + if (!wcpts.empty()) { + if ((graph[source_v].vertex->wcpt().point - wcpts.front().point).magnitude() < 0.01*units::cm) { + start_v = graph[source_v].vertex; + end_v = graph[target_v].vertex; + } else { + end_v = graph[source_v].vertex; + start_v = graph[target_v].vertex; + } + } + + if (start_v && end_v) { + int start_n = boost::out_degree(source_v, graph); + int end_n = boost::out_degree(target_v, graph); + segment_determine_dir_track(sg, start_n, end_n, particle_data, recomb_model, 43000/units::cm, false); + + if ((!sg->particle_info() || sg->dir_weak()) && medium_dQdx/(43e3/units::cm) < 1.3) { + if (!sg->particle_info()) { + sg->particle_info() = std::make_shared(); + } + sg->particle_info()->set_pdg(11); + sg->particle_score(100); + sg->particle_info()->set_mass(particle_data->get_particle_mass(11)); + } + } + } + } + break; + } + } +} From 06bf9f5a0f25fc347efeda68c0c83e079e33260a Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Fri, 2 Jan 2026 15:17:16 -0800 Subject: [PATCH 083/111] implement the determine_main_vertex function --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 1 + clus/src/NeutrinoVertexFinder.cxx | 151 ++++++++++++++++++++ 2 files changed, 152 insertions(+) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 1ab0518b..daa0ae47 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -112,6 +112,7 @@ namespace WireCell::Clus::PR { bool fit_vertex(Facade::Cluster& cluster, VertexPtr vertex, VertexPtr main_vertex, std::set& sg_set, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); void improve_vertex(Graph& graph, Facade::Cluster& cluster, VertexPtr main_vertex, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model, bool flag_search_vertex_activity = true , bool flag_final_vertex = false); + void determine_main_vertex(Graph& graph, Facade::Cluster& cluster, VertexPtr main_vertex, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model, bool flag_print = true); // global information transfer void transfer_info_from_segment_to_cluster(Graph& graph, Facade::Cluster& cluster, const std::string& cloud_name = "associated_points"); diff --git a/clus/src/NeutrinoVertexFinder.cxx b/clus/src/NeutrinoVertexFinder.cxx index e38fb783..26a84bea 100644 --- a/clus/src/NeutrinoVertexFinder.cxx +++ b/clus/src/NeutrinoVertexFinder.cxx @@ -2196,3 +2196,154 @@ void PatternAlgorithms::improve_vertex(Graph& graph, Facade::Cluster& cluster, V } } } + +void PatternAlgorithms::determine_main_vertex(Graph& graph, Facade::Cluster& cluster, VertexPtr main_vertex, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model, bool flag_print){ + + // Find the main vertex - check if we have only showers + bool flag_save_only_showers = true; + for (auto v : boost::make_iterator_range(boost::vertices(graph))) { + VertexPtr vtx = graph[v].vertex; + if (!vtx || vtx->cluster() != &cluster) continue; + + auto results = examine_main_vertex_candidate(graph, vtx); + bool flag_in = std::get<0>(results); + int ntracks = std::get<1>(results); + // int nshowers = std::get<2>(results); + + if (!flag_in) { + if (ntracks > 0) { + flag_save_only_showers = false; + break; + } + } + } + + // Improve vertex if not only showers + if (!flag_save_only_showers) { + improve_vertex(graph, cluster, main_vertex, vertices_in_long_muon, segments_in_long_muon, track_fitter, dv, particle_data, recomb_model, false); + // Fix maps with shower in and track out + fix_maps_shower_in_track_out(graph, cluster); + } + + // Build map of vertex candidates and their track/shower counts + std::map> map_vertex_track_shower; + std::vector main_vertex_candidates; + + for (auto v : boost::make_iterator_range(boost::vertices(graph))) { + VertexPtr vtx = graph[v].vertex; + if (!vtx || vtx->cluster() != &cluster) continue; + + auto results = examine_main_vertex_candidate(graph, vtx); + bool flag_in = std::get<0>(results); + int ntracks = std::get<1>(results); + int nshowers = std::get<2>(results); + + if (!flag_in) { + map_vertex_track_shower[vtx] = std::make_pair(ntracks, nshowers); + } + } + + // Select main vertex candidates based on topology + if (flag_save_only_showers) { + // For all showers case, add vertices with 1 segment first + for (auto v : boost::make_iterator_range(boost::vertices(graph))) { + VertexPtr vtx = graph[v].vertex; + if (!vtx || vtx->cluster() != &cluster) continue; + + int num_segs = 0; + if (vtx->descriptor_valid()) { + auto vd = vtx->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + if (graph[*e_it].segment) num_segs++; + } + } + + if (num_segs == 1) { + main_vertex_candidates.push_back(vtx); + } + } + + // Add remaining candidates + for (auto it = map_vertex_track_shower.begin(); it != map_vertex_track_shower.end(); it++) { + if (std::find(main_vertex_candidates.begin(), main_vertex_candidates.end(), it->first) == main_vertex_candidates.end()) { + main_vertex_candidates.push_back(it->first); + } + } + } else { + // For mixed case, only add vertices with tracks + for (auto it = map_vertex_track_shower.begin(); it != map_vertex_track_shower.end(); it++) { + if (it->second.first > 0) { + main_vertex_candidates.push_back(it->first); + } + } + } + + // Determine main vertex based on candidates + if (flag_save_only_showers) { + if (main_vertex_candidates.size() > 0) { + if (flag_print) { + std::cout << "Determining the main vertex with all showers: " << main_vertex_candidates.size() + << " in cluster " << cluster.get_cluster_id() << std::endl; + } + main_vertex = compare_main_vertices_all_showers(graph, cluster, main_vertex_candidates, track_fitter, dv, particle_data, recomb_model); + } else { + return; + } + } else { + if (flag_print) { + for (auto vtx : main_vertex_candidates) { + std::cout << "Candidate main vertex " << vtx->fit().point << " connecting to: "; + + if (vtx->descriptor_valid()) { + auto vd = vtx->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr sg = graph[*e_it].segment; + if (sg) { + std::cout << sg->id() << ", "; + } + } + } + std::cout << " in cluster " << cluster.get_cluster_id() << std::endl; + } + } + + if (main_vertex_candidates.size() == 1) { + main_vertex = main_vertex_candidates.front(); + } else if (main_vertex_candidates.size() > 1) { + main_vertex = compare_main_vertices(graph, cluster, main_vertex_candidates); + } else { + return; + } + } + + // Examine structure for non-shower cases + if (!flag_save_only_showers) { + examine_structure_final(graph, main_vertex, cluster, track_fitter, dv); + } + + // Examine directions + bool flag_check = examine_direction(graph, main_vertex, main_vertex, vertices_in_long_muon, segments_in_long_muon, particle_data, recomb_model, false); + if (!flag_check) { + std::cout << "Wrong: inconsistency for track directions in cluster " << cluster.get_cluster_id() << std::endl; + } + + if (flag_print) { + std::cout << "Main Vertex " << main_vertex->fit().point << " connecting to: "; + + if (main_vertex->descriptor_valid()) { + auto vd = main_vertex->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr sg = graph[*e_it].segment; + if (sg) { + std::cout << sg->id() << ", "; + } + } + } + std::cout << std::endl; + + print_segs_info(graph, cluster, main_vertex); + } +} \ No newline at end of file From d0bf918473f5d58a9575ef953a4119c5adf56279 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sat, 3 Jan 2026 10:21:37 -0800 Subject: [PATCH 084/111] implement the examine_main_vertices --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 7 + clus/src/NeutrinoPatternBase.cxx | 264 ++++++++++++++++++ clus/src/NeutrinoVertexFinder.cxx | 289 +++++++++++++++++++- 3 files changed, 557 insertions(+), 3 deletions(-) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index daa0ae47..444eef0d 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -113,6 +113,13 @@ namespace WireCell::Clus::PR { bool fit_vertex(Facade::Cluster& cluster, VertexPtr vertex, VertexPtr main_vertex, std::set& sg_set, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); void improve_vertex(Graph& graph, Facade::Cluster& cluster, VertexPtr main_vertex, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model, bool flag_search_vertex_activity = true , bool flag_final_vertex = false); void determine_main_vertex(Graph& graph, Facade::Cluster& cluster, VertexPtr main_vertex, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model, bool flag_print = true); + void change_daughter_type(Graph& graph, VertexPtr vertex, SegmentPtr segment, int particle_type, double mass, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); + void examine_main_vertices(Graph& graph, std::vector& vertices, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); + + // cluster functions ... + Facade::geo_vector_t calc_dir_cluster(Graph& graph, Facade::Cluster& cluster, const Facade::geo_point_t& orig_p, double dis_cut); + Facade::Cluster* swap_main_cluster(Facade::Cluster& new_main_cluster, Facade::Cluster& old_main_cluster, std::vector& other_clusters); + void examine_main_vertices(Graph& graph, std::map map_cluster_main_vertices, Facade::Cluster* main_cluster, std::vector& other_clusters); // global information transfer void transfer_info_from_segment_to_cluster(Graph& graph, Facade::Cluster& cluster, const std::string& cloud_name = "associated_points"); diff --git a/clus/src/NeutrinoPatternBase.cxx b/clus/src/NeutrinoPatternBase.cxx index 88c65755..4bc12131 100644 --- a/clus/src/NeutrinoPatternBase.cxx +++ b/clus/src/NeutrinoPatternBase.cxx @@ -1330,3 +1330,267 @@ std::pair PatternAlgorithms::calc_PCA return std::make_pair(center, PCA_main_axis); } + + +Facade::geo_vector_t PatternAlgorithms::calc_dir_cluster(Graph& graph, Facade::Cluster& cluster, const Facade::geo_point_t& orig_p, double dis_cut){ + Facade::geo_point_t ave_p(0, 0, 0); + int num = 0; + + // Iterate through all segments in the graph that belong to this cluster + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr sg = graph[*eit].segment; + if (!sg || sg->cluster() != &cluster) continue; + + // Get points from this segment (skip first and last points as in original) + const auto& wcpts = sg->wcpts(); + for (size_t i = 1; i + 1 < wcpts.size(); i++) { + const WireCell::Point& pt = wcpts[i].point; + double dis = std::sqrt(std::pow(pt.x() - orig_p.x(), 2) + + std::pow(pt.y() - orig_p.y(), 2) + + std::pow(pt.z() - orig_p.z(), 2)); + + if (dis < dis_cut) { + ave_p.set(ave_p.x() + pt.x(), ave_p.y() + pt.y(), ave_p.z() + pt.z()); + num++; + } + } + } + + // Iterate through all vertices in the graph that belong to this cluster + auto [vbegin, vend] = boost::vertices(graph); + for (auto vit = vbegin; vit != vend; ++vit) { + VertexPtr vtx = graph[*vit].vertex; + if (!vtx || vtx->cluster() != &cluster) continue; + + // Get vertex position (prefer fit point if available) + WireCell::Point vtx_pt = vtx->fit().valid() ? vtx->fit().point : vtx->wcpt().point; + + double dis = std::sqrt(std::pow(vtx_pt.x() - orig_p.x(), 2) + + std::pow(vtx_pt.y() - orig_p.y(), 2) + + std::pow(vtx_pt.z() - orig_p.z(), 2)); + + if (dis < dis_cut) { + ave_p.set(ave_p.x() + vtx_pt.x(), ave_p.y() + vtx_pt.y(), ave_p.z() + vtx_pt.z()); + num++; + } + } + + // Calculate direction vector + Facade::geo_vector_t dir(0, 0, 0); + + if (num > 0) { + // Calculate average position + ave_p.set(ave_p.x() / num, ave_p.y() / num, ave_p.z() / num); + + // Calculate direction from origin to average + dir.set(ave_p.x() - orig_p.x(), ave_p.y() - orig_p.y(), ave_p.z() - orig_p.z()); + + // Normalize to unit vector + double magnitude = std::sqrt(dir.x() * dir.x() + dir.y() * dir.y() + dir.z() * dir.z()); + if (magnitude > 0) { + dir.set(dir.x() / magnitude, dir.y() / magnitude, dir.z() / magnitude); + } + } + + return dir; +} + + + Facade::Cluster* PatternAlgorithms::swap_main_cluster(Facade::Cluster& new_main_cluster, Facade::Cluster& old_main_cluster, std::vector& other_clusters){ + // Remove main_cluster flag from old main cluster (set to 0 to unset) + old_main_cluster.set_flag(Facade::Flags::main_cluster, 0); + + // Add old main cluster to other_clusters + other_clusters.push_back(&old_main_cluster); + + // Set main_cluster flag on new main cluster + new_main_cluster.set_flag(Facade::Flags::main_cluster); + + // Remove new_main_cluster from other_clusters + auto it = std::find_if(other_clusters.begin(), other_clusters.end(), + [&new_main_cluster](Facade::Cluster* c) { + return c == &new_main_cluster; + }); + + if (it != other_clusters.end()) { + other_clusters.erase(it); + } + + return &new_main_cluster; + } + + void PatternAlgorithms::examine_main_vertices(Graph& graph, std::map map_cluster_main_vertices, Facade::Cluster* main_cluster, std::vector& other_clusters){ + if (!main_cluster) return; + + // Calculate cluster length cut + double main_cluster_length = main_cluster->get_length(); + double cluster_length_cut = std::min(main_cluster_length * 0.6, 6.0 * units::cm); + + // First pass: remove short clusters without strong tracks + std::vector clusters_to_be_removed; + + for (auto& [cluster, vertex] : map_cluster_main_vertices) { + if (!cluster || !vertex) continue; + + double length = cluster->get_length(); + + if (length < cluster_length_cut) { + bool flag_removed = true; + + // Check segments connected to this vertex + if (vertex->descriptor_valid()) { + auto vd = vertex->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr seg = graph[*e_it].segment; + if (!seg) continue; + + bool is_shower = seg->flags_any(SegmentFlags::kShowerTrajectory) || + seg->flags_any(SegmentFlags::kShowerTopology); + int dirsign = seg->dirsign(); + bool is_dir_weak = seg->dir_weak(); + double median_dqdx = segment_median_dQ_dx(seg) / (43e3 / units::cm); + + // Keep if: not shower AND has direction AND (strong direction OR high dQ/dx) + if (!is_shower && dirsign != 0 && (!is_dir_weak || median_dqdx > 2.0)) { + flag_removed = false; + break; + } + } + } + + // Additional check for very short clusters + if (!flag_removed) { + if (length < 5 * units::cm) { + WireCell::Point vtx_pt = vertex->fit().valid() ? vertex->fit().point : vertex->wcpt().point; + auto knn = main_cluster->kd_steiner_knn(1, vtx_pt, "steiner_pc"); + if (!knn.empty()) { + double closest_dis = std::sqrt(knn[0].second); + if (closest_dis > 100 * units::cm) { + flag_removed = true; + } + } + } + } + + if (flag_removed) { + clusters_to_be_removed.push_back(cluster); + } + } else { + // For longer clusters, check if very far from main cluster + WireCell::Point vtx_pt = vertex->fit().valid() ? vertex->fit().point : vertex->wcpt().point; + auto knn = main_cluster->kd_steiner_knn(1, vtx_pt, "steiner_pc"); + if (!knn.empty()) { + double closest_dis = std::sqrt(knn[0].second); + if (closest_dis > 200 * units::cm) { + clusters_to_be_removed.push_back(cluster); + } + } + } + } + + // Remove flagged clusters + for (auto cluster : clusters_to_be_removed) { + map_cluster_main_vertices.erase(cluster); + } + clusters_to_be_removed.clear(); + + // Second pass: additional cuts if main cluster has a main vertex + if (map_cluster_main_vertices.find(main_cluster) != map_cluster_main_vertices.end()) { + // Check which clusters have only showers + std::map map_cluster_id_shower; + for (auto& [cluster, vertex] : map_cluster_main_vertices) { + if (cluster) { + map_cluster_id_shower[cluster->get_cluster_id()] = true; + } + } + + // Check all segments to see if any cluster has non-shower segments + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr sg = graph[*eit].segment; + if (!sg) continue; + + int cluster_id = sg->cluster() ? sg->cluster()->get_cluster_id() : -1; + if (map_cluster_id_shower.find(cluster_id) == map_cluster_id_shower.end()) continue; + + bool is_shower = sg->flags_any(SegmentFlags::kShowerTrajectory) || + sg->flags_any(SegmentFlags::kShowerTopology); + if (!is_shower) { + map_cluster_id_shower[cluster_id] = false; + } + } + + bool flag_main_vertex_all_showers = map_cluster_id_shower[main_cluster->get_cluster_id()]; + + if (flag_main_vertex_all_showers) { + // Calculate direction from main cluster's main vertex + VertexPtr main_vtx = map_cluster_main_vertices[main_cluster]; + WireCell::Point main_vtx_pt = main_vtx->fit().valid() ? main_vtx->fit().point : main_vtx->wcpt().point; + Facade::geo_vector_t dir_main = calc_dir_cluster(graph, *main_cluster, main_vtx_pt, 15 * units::cm); + + for (auto& [cluster, vertex] : map_cluster_main_vertices) { + if (cluster == main_cluster || !vertex) continue; + + WireCell::Point vtx_pt = vertex->fit().valid() ? vertex->fit().point : vertex->wcpt().point; + + // Get closest distance to main cluster + auto knn = main_cluster->kd_steiner_knn(1, vtx_pt, "steiner_pc"); + double closest_dis = knn.empty() ? 1e9 : std::sqrt(knn[0].second); + + // Calculate direction vector from main vertex to this vertex + Facade::geo_vector_t dir1(vtx_pt.x() - main_vtx_pt.x(), + vtx_pt.y() - main_vtx_pt.y(), + vtx_pt.z() - main_vtx_pt.z()); + + double angle = 0; + if (dir_main.magnitude() > 0 && dir1.magnitude() > 0) { + double cos_angle = std::clamp(dir_main.dot(dir1) / (dir_main.magnitude() * dir1.magnitude()), -1.0, 1.0); + angle = std::acos(cos_angle) / M_PI * 180.0; + } + + double cluster_length = cluster->get_length(); + + if (angle < 10) { + // Cluster in same direction as main - check if small and close + if ((cluster_length < 15 * units::cm && closest_dis < 40 * units::cm) || + (cluster_length < 7 * units::cm && closest_dis < 60 * units::cm)) { + clusters_to_be_removed.push_back(cluster); + } + } else if (angle > 160) { + // Cluster in opposite direction - check for main cluster swap + if (map_cluster_id_shower[cluster->get_cluster_id()] && + cluster_length > 10 * units::cm && + cluster_length > 0.5 * main_cluster_length) { + + // Calculate direction from this cluster + Facade::geo_vector_t dir2 = calc_dir_cluster(graph, *cluster, vtx_pt, 15 * units::cm); + + // Get closest distance between point clouds + auto closest_result = main_cluster->get_closest_points(*cluster); + double closest_dis_pc = std::get<2>(closest_result); + + double angle2 = 0; + if (dir2.magnitude() > 0 && dir_main.magnitude() > 0) { + double cos_angle2 = std::clamp(dir2.dot(dir_main) / (dir2.magnitude() * dir_main.magnitude()), -1.0, 1.0); + angle2 = std::acos(cos_angle2) / M_PI * 180.0; + } + + if (closest_dis_pc < 10 * units::cm && angle2 < 25) { + // Swap main cluster + main_cluster = swap_main_cluster(*cluster, *main_cluster, other_clusters); + } + } + } + } + } + + // Remove flagged clusters + for (auto cluster : clusters_to_be_removed) { + map_cluster_main_vertices.erase(cluster); + } + clusters_to_be_removed.clear(); + } + } \ No newline at end of file diff --git a/clus/src/NeutrinoVertexFinder.cxx b/clus/src/NeutrinoVertexFinder.cxx index 26a84bea..c4e1d40f 100644 --- a/clus/src/NeutrinoVertexFinder.cxx +++ b/clus/src/NeutrinoVertexFinder.cxx @@ -2218,8 +2218,8 @@ void PatternAlgorithms::determine_main_vertex(Graph& graph, Facade::Cluster& clu } } - // Improve vertex if not only showers - if (!flag_save_only_showers) { + // Improve vertex if not only showers and cluster is main cluster + if (!flag_save_only_showers && cluster.get_flag(Facade::Flags::main_cluster)) { improve_vertex(graph, cluster, main_vertex, vertices_in_long_muon, segments_in_long_muon, track_fitter, dv, particle_data, recomb_model, false); // Fix maps with shower in and track out fix_maps_shower_in_track_out(graph, cluster); @@ -2291,6 +2291,9 @@ void PatternAlgorithms::determine_main_vertex(Graph& graph, Facade::Cluster& clu return; } } else { + // Examine main vertex candidates to filter and identify back-to-back tracks + examine_main_vertices(graph, main_vertex_candidates, particle_data, recomb_model); + if (flag_print) { for (auto vtx : main_vertex_candidates) { std::cout << "Candidate main vertex " << vtx->fit().point << " connecting to: "; @@ -2346,4 +2349,284 @@ void PatternAlgorithms::determine_main_vertex(Graph& graph, Facade::Cluster& clu print_segs_info(graph, cluster, main_vertex); } -} \ No newline at end of file +} + +void PatternAlgorithms::change_daughter_type(Graph& graph, VertexPtr vertex, SegmentPtr segment, int particle_type, double mass, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model){ + // Find the other vertex of this segment + VertexPtr other_vtx = find_other_vertex(graph, segment, vertex); + if (!other_vtx) return; + + // Get vertex point + WireCell::Point other_vtx_pt = other_vtx->fit().valid() ? other_vtx->fit().point : other_vtx->wcpt().point; + + // Calculate direction from the other vertex + Facade::geo_vector_t dir1 = segment_cal_dir_3vector(segment, other_vtx_pt, 15*units::cm); + + // Check if other vertex has valid descriptor + if (!other_vtx->descriptor_valid()) return; + + // Iterate through all segments connected to the other vertex + auto other_vd = other_vtx->get_descriptor(); + auto edge_range = boost::out_edges(other_vd, graph); + + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr sg1 = graph[*e_it].segment; + if (!sg1 || sg1 == segment) continue; + + // Skip if already the same particle type + int current_pdg = sg1->has_particle_info() ? sg1->particle_info()->pdg() : 0; + if (current_pdg == particle_type) continue; + + // Skip if shower trajectory or has strong direction + if (sg1->flags_any(SegmentFlags::kShowerTrajectory)) continue; + if (!sg1->dir_weak() && sg1->dirsign() != 0) continue; + + // Check shower topology case: long segments with no direction + if (sg1->flags_any(SegmentFlags::kShowerTopology) && + sg1->dirsign() == 0 && + segment_track_length(sg1) > 40*units::cm) { + + // Calculate direction at 40cm + Facade::geo_vector_t dir2 = segment_cal_dir_3vector(sg1, other_vtx_pt, 40*units::cm); + + if (dir1.magnitude() > 0 && dir2.magnitude() > 0) { + double cos_angle = std::clamp(dir1.dot(dir2) / (dir1.magnitude() * dir2.magnitude()), -1.0, 1.0); + double angle = std::acos(cos_angle) / M_PI * 180.0; + + if (angle > 170) { + // Change particle type and mass + if (!sg1->particle_info()) { + sg1->particle_info() = std::make_shared(); + } + sg1->particle_info()->set_pdg(particle_type); + sg1->particle_info()->set_mass(mass); + sg1->unset_flags(SegmentFlags::kShowerTopology); + + // Recursively propagate changes + change_daughter_type(graph, other_vtx, sg1, particle_type, mass, particle_data, recomb_model); + VertexPtr sg1_other_vtx = find_other_vertex(graph, sg1, other_vtx); + if (sg1_other_vtx) { + change_daughter_type(graph, sg1_other_vtx, sg1, particle_type, mass, particle_data, recomb_model); + } + } + } + continue; + } else if (sg1->flags_any(SegmentFlags::kShowerTopology) && + sg1->dirsign() == 0 && + segment_track_length(sg1) <= 40*units::cm) { + continue; + } + + // Check general case: segments longer than 10cm + if (segment_track_length(sg1) > 10*units::cm) { + // Calculate direction at 15cm + Facade::geo_vector_t dir2 = segment_cal_dir_3vector(sg1, other_vtx_pt, 15*units::cm); + + if (dir1.magnitude() > 0 && dir2.magnitude() > 0) { + double cos_angle = std::clamp(dir1.dot(dir2) / (dir1.magnitude() * dir2.magnitude()), -1.0, 1.0); + double angle = std::acos(cos_angle) / M_PI * 180.0; + + if (angle > 165) { + // Change particle type and mass + if (!sg1->particle_info()) { + sg1->particle_info() = std::make_shared(); + } + sg1->particle_info()->set_pdg(particle_type); + sg1->particle_info()->set_mass(mass); + + // Recursively propagate changes + change_daughter_type(graph, other_vtx, sg1, particle_type, mass, particle_data, recomb_model); + VertexPtr sg1_other_vtx = find_other_vertex(graph, sg1, other_vtx); + if (sg1_other_vtx) { + change_daughter_type(graph, sg1_other_vtx, sg1, particle_type, mass, particle_data, recomb_model); + } + } + } + } + } +} + +void PatternAlgorithms::examine_main_vertices(Graph& graph, std::vector& vertices, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model){ + if (vertices.size() == 1) return; + + double max_length = 0; + std::set tmp_vertices; + + for (auto vtx : vertices) { + if (!vtx || !vtx->descriptor_valid()) continue; + + // Count segments connected to this vertex + int num_segs = 0; + auto vd = vtx->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + if (graph[*e_it].segment) num_segs++; + } + + // If only 1 segment, add to tmp_vertices + if (num_segs == 1) { + tmp_vertices.insert(vtx); + } else { + // Check pairs of segments for back-to-back tracks + std::set used_segments; + WireCell::Point vtx_pt = vtx->fit().valid() ? vtx->fit().point : vtx->wcpt().point; + + // Collect all segments and check pairs + std::vector vertex_segments; + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr sg = graph[*e_it].segment; + if (sg) vertex_segments.push_back(sg); + } + + for (size_t i = 0; i < vertex_segments.size(); i++) { + SegmentPtr sg1 = vertex_segments[i]; + double length1 = segment_track_length(sg1); + if (length1 < 10*units::cm) continue; + + Facade::geo_vector_t dir1 = segment_cal_dir_3vector(sg1, vtx_pt, 15*units::cm); + Facade::geo_vector_t dir3 = segment_cal_dir_3vector(sg1, vtx_pt, 30*units::cm); + + if (length1 > max_length) max_length = length1; + + for (size_t j = i + 1; j < vertex_segments.size(); j++) { + SegmentPtr sg2 = vertex_segments[j]; + double length2 = segment_track_length(sg2); + if (length2 < 10*units::cm) continue; + + Facade::geo_vector_t dir2 = segment_cal_dir_3vector(sg2, vtx_pt, 15*units::cm); + Facade::geo_vector_t dir4 = segment_cal_dir_3vector(sg2, vtx_pt, 30*units::cm); + + double angle1 = 0, angle2 = 0; + if (dir1.magnitude() > 0 && dir2.magnitude() > 0) { + double cos_angle = std::clamp(dir1.dot(dir2) / (dir1.magnitude() * dir2.magnitude()), -1.0, 1.0); + angle1 = std::acos(cos_angle) / M_PI * 180.0; + } + if (dir3.magnitude() > 0 && dir4.magnitude() > 0) { + double cos_angle = std::clamp(dir3.dot(dir4) / (dir3.magnitude() * dir4.magnitude()), -1.0, 1.0); + angle2 = std::acos(cos_angle) / M_PI * 180.0; + } + + int pdg1 = sg1->has_particle_info() ? sg1->particle_info()->pdg() : 0; + int pdg2 = sg2->has_particle_info() ? sg2->particle_info()->pdg() : 0; + + // Check for back-to-back muon tracks + if ((angle1 > 165 || angle2 > 165) && + (pdg1 == 13 || pdg2 == 13) && + (length1 > 30*units::cm || length2 > 30*units::cm)) { + used_segments.insert(sg1); + used_segments.insert(sg2); + } + // Check for back-to-back proton tracks + else if ((angle1 > 170 || angle2 > 170) && + ((pdg1 == 2212 && (pdg2 == 0 || pdg2 == 2212)) || + (pdg2 == 2212 && (pdg1 == 0 || pdg1 == 2212))) && + (length1 > 20*units::cm && length2 > 20*units::cm)) { + used_segments.insert(sg1); + used_segments.insert(sg2); + } + } + } + + // If we found back-to-back tracks, check remaining segments + if (used_segments.size() > 0) { + bool flag_skip = true; + + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr sg1 = graph[*e_it].segment; + if (!sg1) continue; + if (used_segments.find(sg1) != used_segments.end()) continue; + + double length = segment_track_length(sg1); + bool is_shower = sg1->flags_any(SegmentFlags::kShowerTrajectory) || + sg1->flags_any(SegmentFlags::kShowerTopology); + + if (is_shower) { + // Check shower significance + auto pair_result = calculate_num_daughter_showers(graph, vtx, sg1, false); + if (pair_result.second > 35*units::cm) { + flag_skip = false; + break; + } + } else { + // Check track significance + if (!sg1->dir_weak() && length > 6*units::cm) { + flag_skip = false; + break; + } + } + } + + if (!flag_skip) { + tmp_vertices.insert(vtx); + } else { + // Change particle types to muons for back-to-back tracks + double muon_mass = particle_data->get_particle_mass(13); + + for (auto sg1 : used_segments) { + // Skip shower trajectory + if (sg1->flags_any(SegmentFlags::kShowerTrajectory)) continue; + + // Handle shower topology + if (sg1->flags_any(SegmentFlags::kShowerTopology)) { + if (segment_track_length(sg1) > 40*units::cm && sg1->dirsign() == 0) { + if (!sg1->particle_info()) { + sg1->particle_info() = std::make_shared(); + } + sg1->particle_info()->set_pdg(13); + sg1->particle_info()->set_mass(muon_mass); + sg1->unset_flags(SegmentFlags::kShowerTopology); + + change_daughter_type(graph, vtx, sg1, 13, muon_mass, particle_data, recomb_model); + VertexPtr other_vtx = find_other_vertex(graph, sg1, vtx); + if (other_vtx) { + change_daughter_type(graph, other_vtx, sg1, 13, muon_mass, particle_data, recomb_model); + } + } else { + continue; + } + } + // Handle non-muon particles + else { + int current_pdg = sg1->has_particle_info() ? sg1->particle_info()->pdg() : 0; + if (current_pdg != 13) { + if (!sg1->particle_info()) { + sg1->particle_info() = std::make_shared(); + } + sg1->particle_info()->set_pdg(13); + sg1->particle_info()->set_mass(muon_mass); + + change_daughter_type(graph, vtx, sg1, 13, muon_mass, particle_data, recomb_model); + VertexPtr other_vtx = find_other_vertex(graph, sg1, vtx); + if (other_vtx) { + change_daughter_type(graph, other_vtx, sg1, 13, muon_mass, particle_data, recomb_model); + } + } + } + + // Find continuation muon segments and add final vertex + std::vector acc_vertices; + auto results = find_cont_muon_segment(graph, sg1, vtx); + while (results.first != nullptr) { + acc_vertices.push_back(results.second); + results = find_cont_muon_segment(graph, results.first, results.second); + } + + if (acc_vertices.size() > 0 && acc_vertices.back() != nullptr) { + tmp_vertices.insert(acc_vertices.back()); + } + } + } + } else { + // No back-to-back tracks found, keep this vertex + tmp_vertices.insert(vtx); + } + } + } + + // Update vertices collection + if (tmp_vertices.size() == 0) return; + + vertices.clear(); + vertices.resize(tmp_vertices.size()); + std::copy(tmp_vertices.begin(), tmp_vertices.end(), vertices.begin()); +} From a69dd913cfa2f565bf43c3d16a7a0f8b75873890 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sat, 3 Jan 2026 11:35:55 -0800 Subject: [PATCH 085/111] implement moer functions --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 4 + clus/src/NeutrinoVertexFinder.cxx | 291 ++++++++++++++++++++ 2 files changed, 295 insertions(+) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 444eef0d..be186f33 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -121,6 +121,10 @@ namespace WireCell::Clus::PR { Facade::Cluster* swap_main_cluster(Facade::Cluster& new_main_cluster, Facade::Cluster& old_main_cluster, std::vector& other_clusters); void examine_main_vertices(Graph& graph, std::map map_cluster_main_vertices, Facade::Cluster* main_cluster, std::vector& other_clusters); + VertexPtr compare_main_vertices_global(Graph& graph, std::vector& vertex_candidates, Facade::Cluster& main_cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); + Facade::Cluster* check_switch_main_cluster(Graph& graph, std::map map_cluster_main_vertices, Facade::Cluster* main_cluster, std::vector& other_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); + Facade::Cluster* check_switch_main_cluster_2(Graph& graph, VertexPtr temp_main_vertex, Facade::Cluster* max_length_cluster, Facade::Cluster* main_cluster, std::vector& other_clusters); + // global information transfer void transfer_info_from_segment_to_cluster(Graph& graph, Facade::Cluster& cluster, const std::string& cloud_name = "associated_points"); diff --git a/clus/src/NeutrinoVertexFinder.cxx b/clus/src/NeutrinoVertexFinder.cxx index c4e1d40f..f2a786ab 100644 --- a/clus/src/NeutrinoVertexFinder.cxx +++ b/clus/src/NeutrinoVertexFinder.cxx @@ -2630,3 +2630,294 @@ void PatternAlgorithms::examine_main_vertices(Graph& graph, std::vector& vertex_candidates, Facade::Cluster& main_cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv){ + if (vertex_candidates.empty()) return nullptr; + + bool flag_print = false; + + // Initialize scoring map + std::map map_vertex_num; + for (auto vtx : vertex_candidates) { + map_vertex_num[vtx] = 0; + } + + // Score based on z position (prefer earlier/upstream vertices) + double min_z = 1e9; + for (auto vtx : vertex_candidates) { + WireCell::Point vtx_pt = vtx->fit().valid() ? vtx->fit().point : vtx->wcpt().point; + if (vtx_pt.z() < min_z) min_z = vtx_pt.z(); + } + + for (auto vtx : vertex_candidates) { + WireCell::Point vtx_pt = vtx->fit().valid() ? vtx->fit().point : vtx->wcpt().point; + map_vertex_num[vtx] -= (vtx_pt.z() - min_z) / (200 * units::cm); + + // Score based on segments connected to this vertex + if (vtx->descriptor_valid()) { + auto vd = vtx->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr sg = graph[*e_it].segment; + if (!sg) continue; + + bool is_shower = sg->flags_any(SegmentFlags::kShowerTrajectory) || + sg->flags_any(SegmentFlags::kShowerTopology); + + if (is_shower) { + map_vertex_num[vtx] += 1.0 / 4.0 / 2.0; // showers count less + } else { + map_vertex_num[vtx] += 1.0 / 4.0; // tracks + } + + // Bonus for clear protons or tracks with direction + int pdg = sg->has_particle_info() ? sg->particle_info()->pdg() : 0; + int dirsign = sg->dirsign(); + bool is_dir_weak = sg->dir_weak(); + + if (pdg == 2212 && dirsign != 0 && !is_dir_weak) { + map_vertex_num[vtx] += 1.0 / 4.0; // clear proton + } else if (dirsign != 0 && !is_shower) { + map_vertex_num[vtx] += 1.0 / 4.0 / 2.0; // track with direction + } + } + } + + // Bonus if vertex is in main cluster + if (vtx->cluster() == &main_cluster) { + map_vertex_num[vtx] += 0.25; + } + + if (flag_print) { + std::cout << "A: " << map_vertex_num[vtx] << " " << (vtx_pt.z() - min_z) / (200 * units::cm) << std::endl; + } + } + + // Score based on fiducial volume + auto grouping = main_cluster.grouping(); + auto fiducial_utils = grouping ? grouping->get_fiducialutils() : nullptr; + + for (auto vtx : vertex_candidates) { + WireCell::Point vtx_pt = vtx->fit().valid() ? vtx->fit().point : vtx->wcpt().point; + + bool in_fv = fiducial_utils && fiducial_utils->inside_fiducial_volume(vtx_pt); + if (in_fv || vtx->cluster() == &main_cluster) { + map_vertex_num[vtx] += 0.5; + } + + if (flag_print) { + std::cout << "B: " << map_vertex_num[vtx] << " " << in_fv << std::endl; + } + } + + // Calculate direction for each vertex + std::map map_vertex_dir; + for (auto vtx : vertex_candidates) { + map_vertex_dir[vtx] = vertex_get_dir(vtx, graph, 5 * units::cm); + } + + // Score based on whether other vertices point toward this vertex + for (auto vtx : vertex_candidates) { + WireCell::Point vtx_pt = vtx->fit().valid() ? vtx->fit().point : vtx->wcpt().point; + double delta = 0; + + for (auto vtx1 : vertex_candidates) { + if (vtx1 == vtx) continue; + + WireCell::Point vtx1_pt = vtx1->fit().valid() ? vtx1->fit().point : vtx1->wcpt().point; + + // Direction from vtx to vtx1 + Facade::geo_vector_t dir(vtx1_pt.x() - vtx_pt.x(), + vtx1_pt.y() - vtx_pt.y(), + vtx1_pt.z() - vtx_pt.z()); + + Facade::geo_vector_t dir1 = map_vertex_dir[vtx1]; + + if (dir.magnitude() > 0 && dir1.magnitude() > 0) { + double cos_angle = std::clamp(dir.dot(dir1) / (dir.magnitude() * dir1.magnitude()), -1.0, 1.0); + double angle = std::acos(cos_angle) / M_PI * 180.0; + + if (angle < 15) { + map_vertex_num[vtx] += 0.25; + delta++; + } else if (angle < 30) { + map_vertex_num[vtx] += 0.25 / 2.0; + delta++; + } + } + } + + // Penalize isolated vertices not in main cluster + if (delta == 0) { + double total_length = 0; + int num_tracks = 0; + + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr seg = graph[*eit].segment; + if (!seg || seg->cluster() != vtx->cluster()) continue; + + total_length += segment_track_length(seg); + num_tracks++; + } + + if (vtx->cluster() != &main_cluster && total_length < 6 * units::cm) { + map_vertex_num[vtx] -= 0.25 * num_tracks; + } + } + + if (flag_print) { + std::cout << "E: " << map_vertex_num[vtx] << std::endl; + } + } + + // Find vertex with maximum score + double max_val = -1e9; + VertexPtr max_vertex = nullptr; + + for (auto vtx : vertex_candidates) { + if (map_vertex_num[vtx] > max_val) { + max_val = map_vertex_num[vtx]; + max_vertex = vtx; + } + } + + return max_vertex; +} + +Facade::Cluster* PatternAlgorithms::check_switch_main_cluster(Graph& graph, std::map map_cluster_main_vertices, Facade::Cluster* main_cluster, std::vector& other_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv){ + if (!main_cluster) return main_cluster; + + bool flag_all_showers = false; + bool flag_print = true; + + VertexPtr temp_main_vertex = nullptr; + + // Check if main cluster has a main vertex + if (map_cluster_main_vertices.find(main_cluster) != map_cluster_main_vertices.end()) { + temp_main_vertex = map_cluster_main_vertices[main_cluster]; + int n_showers = 0; + int n_total = 0; + + // Count showers connected to this vertex + if (temp_main_vertex && temp_main_vertex->descriptor_valid()) { + auto vd = temp_main_vertex->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr seg = graph[*e_it].segment; + if (!seg) continue; + + n_total++; + bool is_shower = seg->flags_any(SegmentFlags::kShowerTrajectory) || + seg->flags_any(SegmentFlags::kShowerTopology); + if (is_shower) n_showers++; + } + } + + if (n_total > 0 && n_showers == n_total) { + flag_all_showers = true; + } + } else { + flag_all_showers = true; + } + + // If all showers, consider switching main cluster + if (flag_all_showers) { + // Collect all vertex candidates + std::vector vertex_candidates; + for (auto& [cluster, vertex] : map_cluster_main_vertices) { + if (vertex) { + vertex_candidates.push_back(vertex); + } + } + + if (flag_print) { + for (auto vtx : vertex_candidates) { + WireCell::Point vtx_pt = vtx->fit().valid() ? vtx->fit().point : vtx->wcpt().point; + int cluster_id = vtx->cluster() ? vtx->cluster()->get_cluster_id() : -1; + std::cout << "Candidate main vertex " << cluster_id << " " << vtx_pt << " connecting to: "; + + if (vtx->descriptor_valid()) { + auto vd = vtx->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr seg = graph[*e_it].segment; + if (seg) { + std::cout << seg->id() << ", "; + } + } + } + std::cout << std::endl; + } + } + + // Compare all vertex candidates to find the best one + VertexPtr temp_main_vertex_1 = nullptr; + if (!vertex_candidates.empty()) { + temp_main_vertex_1 = compare_main_vertices_global(graph, vertex_candidates, *main_cluster, track_fitter, dv); + } + + // Check if we should switch + if (temp_main_vertex_1 && temp_main_vertex_1 != temp_main_vertex) { + if (temp_main_vertex) { + int old_id = temp_main_vertex->cluster() ? temp_main_vertex->cluster()->get_cluster_id() : -1; + int new_id = temp_main_vertex_1->cluster() ? temp_main_vertex_1->cluster()->get_cluster_id() : -1; + std::cout << "Switch Main Cluster " << old_id << " to " << new_id << std::endl; + } else { + int new_id = temp_main_vertex_1->cluster() ? temp_main_vertex_1->cluster()->get_cluster_id() : -1; + std::cout << "Switch Main Cluster to " << new_id << std::endl; + } + + // Find which cluster this vertex belongs to and swap + for (auto& [cluster, vertex] : map_cluster_main_vertices) { + if (vertex == temp_main_vertex_1 && cluster != main_cluster) { + main_cluster = swap_main_cluster(*cluster, *main_cluster, other_clusters); + break; + } + } + } + } + + return main_cluster; +} + +Facade::Cluster* PatternAlgorithms::check_switch_main_cluster_2(Graph& graph, VertexPtr temp_main_vertex, Facade::Cluster* max_length_cluster, Facade::Cluster* main_cluster, std::vector& other_clusters){ + if (!temp_main_vertex || !max_length_cluster || !main_cluster) return main_cluster; + + bool flag_switch = false; + + // Count showers connected to this vertex + int n_showers = 0; + int n_total = 0; + + if (temp_main_vertex->descriptor_valid()) { + auto vd = temp_main_vertex->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr seg = graph[*e_it].segment; + if (!seg) continue; + + n_total++; + bool is_shower = seg->flags_any(SegmentFlags::kShowerTrajectory) || + seg->flags_any(SegmentFlags::kShowerTopology); + if (is_shower) n_showers++; + } + } + + // If all segments are showers, consider switching + if (n_total > 0 && n_showers == n_total) { + flag_switch = true; + } + + if (flag_switch) { + std::cout << "Switch Main Cluster " << main_cluster->get_cluster_id() + << " to " << max_length_cluster->get_cluster_id() << std::endl; + main_cluster = swap_main_cluster(*max_length_cluster, *main_cluster, other_clusters); + } + + return main_cluster; +} From e089ca5cc5f7a3f6a4af05c89b637f0c833b09ad Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sat, 3 Jan 2026 11:52:06 -0800 Subject: [PATCH 086/111] update code --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 3 +- clus/src/NeutrinoVertexFinder.cxx | 124 +++++++++++++++++++- 2 files changed, 124 insertions(+), 3 deletions(-) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index be186f33..efdd5762 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -114,7 +114,7 @@ namespace WireCell::Clus::PR { void improve_vertex(Graph& graph, Facade::Cluster& cluster, VertexPtr main_vertex, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model, bool flag_search_vertex_activity = true , bool flag_final_vertex = false); void determine_main_vertex(Graph& graph, Facade::Cluster& cluster, VertexPtr main_vertex, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model, bool flag_print = true); void change_daughter_type(Graph& graph, VertexPtr vertex, SegmentPtr segment, int particle_type, double mass, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); - void examine_main_vertices(Graph& graph, std::vector& vertices, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); + void examine_main_vertices_local(Graph& graph, std::vector& vertices, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); // cluster functions ... Facade::geo_vector_t calc_dir_cluster(Graph& graph, Facade::Cluster& cluster, const Facade::geo_point_t& orig_p, double dis_cut); @@ -124,6 +124,7 @@ namespace WireCell::Clus::PR { VertexPtr compare_main_vertices_global(Graph& graph, std::vector& vertex_candidates, Facade::Cluster& main_cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); Facade::Cluster* check_switch_main_cluster(Graph& graph, std::map map_cluster_main_vertices, Facade::Cluster* main_cluster, std::vector& other_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); Facade::Cluster* check_switch_main_cluster_2(Graph& graph, VertexPtr temp_main_vertex, Facade::Cluster* max_length_cluster, Facade::Cluster* main_cluster, std::vector& other_clusters); + VertexPtr determine_overall_main_vertex(Graph& graph, std::map map_cluster_main_vertices, Facade::Cluster* main_cluster, std::vector& other_clusters, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model ); // global information transfer void transfer_info_from_segment_to_cluster(Graph& graph, Facade::Cluster& cluster, const std::string& cloud_name = "associated_points"); diff --git a/clus/src/NeutrinoVertexFinder.cxx b/clus/src/NeutrinoVertexFinder.cxx index f2a786ab..ec4bb1d8 100644 --- a/clus/src/NeutrinoVertexFinder.cxx +++ b/clus/src/NeutrinoVertexFinder.cxx @@ -2292,7 +2292,7 @@ void PatternAlgorithms::determine_main_vertex(Graph& graph, Facade::Cluster& clu } } else { // Examine main vertex candidates to filter and identify back-to-back tracks - examine_main_vertices(graph, main_vertex_candidates, particle_data, recomb_model); + examine_main_vertices_local(graph, main_vertex_candidates, particle_data, recomb_model); if (flag_print) { for (auto vtx : main_vertex_candidates) { @@ -2446,7 +2446,7 @@ void PatternAlgorithms::change_daughter_type(Graph& graph, VertexPtr vertex, Seg } } -void PatternAlgorithms::examine_main_vertices(Graph& graph, std::vector& vertices, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model){ +void PatternAlgorithms::examine_main_vertices_local(Graph& graph, std::vector& vertices, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model){ if (vertices.size() == 1) return; double max_length = 0; @@ -2921,3 +2921,123 @@ Facade::Cluster* PatternAlgorithms::check_switch_main_cluster_2(Graph& graph, Ve return main_cluster; } + +VertexPtr PatternAlgorithms::determine_overall_main_vertex(Graph& graph, std::map map_cluster_main_vertices, Facade::Cluster* main_cluster, std::vector& other_clusters, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model ){ + if (!main_cluster) return nullptr; + + // Find cluster with maximum length + Facade::Cluster* max_length_cluster = nullptr; + double max_length = 0; + + // Check all clusters in the map + for (auto& [cluster, vertex] : map_cluster_main_vertices) { + if (!cluster) continue; + double length = cluster->get_length(); + if (length > max_length) { + max_length = length; + max_length_cluster = cluster; + } + } + + // Also check main cluster if not in map + if (map_cluster_main_vertices.find(main_cluster) == map_cluster_main_vertices.end()) { + double main_length = main_cluster->get_length(); + if (main_length > max_length) { + max_length = main_length; + max_length_cluster = main_cluster; + } + } + + // Check all other clusters + for (auto cluster : other_clusters) { + if (!cluster) continue; + double length = cluster->get_length(); + if (length > max_length) { + max_length = length; + max_length_cluster = cluster; + } + } + + // Examine main vertices first + examine_main_vertices(graph, map_cluster_main_vertices, main_cluster, other_clusters); + + // Check for main cluster switch + // For now, using simplified version (similar to frozen chain in original) + if (max_length_cluster && main_cluster) { + double main_length = main_cluster->get_length(); + if (max_length > main_length * 0.8) { + VertexPtr temp_main_vertex = map_cluster_main_vertices.find(main_cluster) != map_cluster_main_vertices.end() + ? map_cluster_main_vertices[main_cluster] : nullptr; + if (temp_main_vertex) { + main_cluster = check_switch_main_cluster_2(graph, temp_main_vertex, max_length_cluster, main_cluster, + other_clusters); + } + } + } + + // Get the main vertex + VertexPtr main_vertex = nullptr; + if (map_cluster_main_vertices.find(main_cluster) != map_cluster_main_vertices.end()) { + main_vertex = map_cluster_main_vertices[main_cluster]; + } + + if (!main_vertex) return nullptr; + + // Examine tracks connected to main vertex - look for short high dQ/dx proton candidates + if (main_vertex->descriptor_valid()) { + auto vd = main_vertex->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr sg = graph[*e_it].segment; + if (!sg) continue; + + auto pair_results = calculate_num_daughter_showers(graph, main_vertex, sg, false); + double length = segment_track_length(sg); + double median_dqdx = segment_median_dQ_dx(sg) / (43e3 / units::cm); + + // Short segment with only 1 daughter and high dQ/dx -> likely proton + if (pair_results.first == 1 && length < 1.5 * units::cm && median_dqdx > 1.6) { + if (!sg->particle_info()) { + sg->particle_info() = std::make_shared(); + } + sg->particle_info()->set_pdg(2212); // proton + sg->particle_info()->set_mass(particle_data->get_particle_mass(2212)); + + // Calculate 4-momentum + segment_cal_4mom(sg, 2212, particle_data, recomb_model); + } + } + } + + // Clean up long muons - remove segments/vertices not in main cluster + { + std::set tmp_segments; + std::set tmp_vertices; + + // Find segments not in main cluster + for (auto seg : segments_in_long_muon) { + if (seg && seg->cluster() != main_cluster) { + tmp_segments.insert(seg); + } + } + + // Find vertices not in main cluster + for (auto vtx : vertices_in_long_muon) { + if (vtx && vtx->cluster() != main_cluster) { + tmp_vertices.insert(vtx); + } + } + + // Remove them from the long muon sets + for (auto seg : tmp_segments) { + segments_in_long_muon.erase(seg); + } + + for (auto vtx : tmp_vertices) { + vertices_in_long_muon.erase(vtx); + } + } + + return main_vertex; +} \ No newline at end of file From f131e01723b78485d620ddf6c8b4a528ac959ce1 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sun, 4 Jan 2026 07:46:49 -0800 Subject: [PATCH 087/111] add more functions --- clus/inc/WireCellClus/PRShower.h | 55 ++++- clus/inc/WireCellClus/PRTrajectoryView.h | 6 + clus/src/PRShower.cxx | 283 ++++++++++++++++++++++- clus/src/PRShowerFunctions.cxx | 2 + 4 files changed, 341 insertions(+), 5 deletions(-) create mode 100644 clus/src/PRShowerFunctions.cxx diff --git a/clus/inc/WireCellClus/PRShower.h b/clus/inc/WireCellClus/PRShower.h index edaa9f52..f81cf8d6 100644 --- a/clus/inc/WireCellClus/PRShower.h +++ b/clus/inc/WireCellClus/PRShower.h @@ -62,6 +62,7 @@ namespace WireCell::Clus::PR { public: Shower(Graph& graph); + virtual ~Shower(); // The bag of attributes is directly exposed to user. @@ -96,17 +97,67 @@ namespace WireCell::Clus::PR { the underlying graph, this function is a no-op and the stored start_vertex is nullified. */ - Shower& start_vertex(VertexPtr vtx); + Shower& set_start_vertex(VertexPtr vtx, int type); + std::pair get_start_vertex_and_type() { + return std::make_pair(m_start_vertex, data.start_connection_type); + } /** Chainable setter of start segment. This has the same semantics and caveats as the chainable setter: `start_vertex(VertexPtr)`. */ - Shower& start_segment(SegmentPtr seg); + Shower& set_start_segment(SegmentPtr seg, bool flag_include_vertices = false, const std::string& cloud_name_fit = "fit", const std::string& cloud_name_associate = "associate_points"); + + Shower& set_start_point( WireCell::Point pt ) { + data.start_point = pt; + return *this; + } + WireCell::Point get_start_point() const { + return data.start_point; + } + WireCell::Point get_end_point() const { + return data.end_point; + } + + void add_segment(SegmentPtr seg, bool flag_include_vertices = false, const std::string& cloud_name_fit = "fit", const std::string& cloud_name_associate = "associate_points"); + + // particle type + int get_particle_type(){return data.particle_type;}; + void set_particle_type(int val){data.particle_type = val;}; + + // access flags + void set_flag_kinematics(bool val); + bool get_flag_kinematics(); + bool get_flag_shower(); + + // return kinematic energy estimation + double get_kine_range(){return data.kenergy_range;}; + double get_kine_dQdx(){return data.kenergy_dQdx;}; + void set_kine_charge(double val){data.kenergy_charge = val;}; + double get_kine_charge(){return data.kenergy_charge;}; + double get_kine_best(){return data.kenergy_best;}; + + // return initial direction ... + WireCell::Vector& get_init_dir(){return data.init_dir;}; + + // Get point cloud by name (convenience wrapper around dpcloud) + std::shared_ptr get_pcloud(const std::string& cloud_name = "fit") { + return this->dpcloud(cloud_name); + } + std::shared_ptr get_pcloud(const std::string& cloud_name = "associate_points") const { + return this->dpcloud(cloud_name); + } + + // Add all segments and vertices from another shower to this one + void add_shower(Shower& temp_shower, const std::string& cloud_name_fit = "fit", const std::string& cloud_name_associate = "associate_points"); + void complete_structure_with_start_segment(std::set& used_segments, const std::string& cloud_name_fit = "fit", const std::string& cloud_name_associate = "associate_points"); + + private: + Graph& m_full_graph; VertexPtr m_start_vertex; SegmentPtr m_start_segment; diff --git a/clus/inc/WireCellClus/PRTrajectoryView.h b/clus/inc/WireCellClus/PRTrajectoryView.h index 5d337eda..b51d9ada 100644 --- a/clus/inc/WireCellClus/PRTrajectoryView.h +++ b/clus/inc/WireCellClus/PRTrajectoryView.h @@ -104,6 +104,12 @@ namespace WireCell::Clus::PR { */ bool remove_segment(SegmentPtr seg); + /** Get the set of node descriptors in this view. */ + const node_unordered_set& nodes() const { return m_nodes; } + + /** Get the set of edge descriptors in this view. */ + const edge_unordered_set& edges() const { return m_edges; } + private: view_graph_type m_graph; node_unordered_set m_nodes; diff --git a/clus/src/PRShower.cxx b/clus/src/PRShower.cxx index 9bf336dd..0d6a4ba6 100644 --- a/clus/src/PRShower.cxx +++ b/clus/src/PRShower.cxx @@ -1,10 +1,34 @@ #include "WireCellClus/PRShower.h" #include "WireCellClus/PRGraph.h" +#include "WireCellClus/DynamicPointCloud.h" namespace WireCell::Clus::PR { + + // Default initialization constructor following WCPPID WCShower logic Shower::Shower(Graph& graph) : TrajectoryView(graph) + , m_full_graph(graph) { + // Initialize all ShowerData members to defaults + data.particle_type = 0; + data.kenergy_range = 0; + data.kenergy_dQdx = 0; + data.kenergy_charge = 0; + data.kenergy_best = 0; + + data.start_point = Point(0, 0, 0); + data.end_point = Point(0, 0, 0); + data.init_dir = Vector(0, 0, 0); + + data.start_connection_type = 0; + + // Initialize start vertex and segment to nullptr + m_start_vertex = nullptr; + m_start_segment = nullptr; + + // Set shower flag (following WCPPID) + set_flags(ShowerFlags::kShower); + unset_flags(ShowerFlags::kKinematics); } Shower::~Shower() @@ -26,7 +50,7 @@ namespace WireCell::Clus::PR { // Chainable setters /// Chainable setter of start vertex - Shower& Shower::start_vertex(VertexPtr vtx) + Shower& Shower::set_start_vertex(VertexPtr vtx, int type) { if (! vtx->descriptor_valid()) { m_start_vertex = nullptr; @@ -34,12 +58,16 @@ namespace WireCell::Clus::PR { } this->add_vertex(vtx); m_start_vertex = vtx; + data.start_connection_type = type; + + return *this; } + /// Chainable setter of start segment - Shower& Shower::start_segment(SegmentPtr seg) + Shower& Shower::set_start_segment(SegmentPtr seg, bool flag_include_vertices, const std::string& cloud_name_fit, const std::string& cloud_name_associate) { if (! seg->descriptor_valid()) { m_start_segment = nullptr; @@ -47,9 +75,258 @@ namespace WireCell::Clus::PR { } this->add_segment(seg); m_start_segment = seg; + + // If flag_include_vertices is true, add all vertices connected to this segment + if (flag_include_vertices) { + // Get the two vertices connected to this segment from the full graph + auto vertices = find_vertices(m_full_graph, seg); + + // Add each vertex to the view (skip the start_vertex) + if (vertices.first && vertices.first != m_start_vertex) { + this->add_vertex(vertices.first); + } + if (vertices.second && vertices.second != m_start_vertex) { + this->add_vertex(vertices.second); + } + } + + // Merge dynamic point clouds from segment to shower with the provided names + if (!cloud_name_fit.empty()) { + auto seg_dpc_fit = seg->dpcloud(cloud_name_fit); + if (seg_dpc_fit) { + auto shower_dpc_fit = this->dpcloud(cloud_name_fit); + if (!shower_dpc_fit) { + // Create new DPC if it doesn't exist in shower, copying wpid_params from segment's DPC + // We need the wpid_params to construct a new DPC, but it's private + // For now, just share the pointer - we'll need to modify this if independent DPCs are needed + this->dpcloud(cloud_name_fit, seg_dpc_fit); + } else { + // Add points from segment's DPC to existing shower's DPC + shower_dpc_fit->add_points(seg_dpc_fit->get_points()); + } + } + } + + if (!cloud_name_associate.empty()) { + auto seg_dpc_associate = seg->dpcloud(cloud_name_associate); + if (seg_dpc_associate) { + auto shower_dpc_associate = this->dpcloud(cloud_name_associate); + if (!shower_dpc_associate) { + // Create new DPC if it doesn't exist in shower + this->dpcloud(cloud_name_associate, seg_dpc_associate); + } else { + // Add points from segment's DPC to existing shower's DPC + shower_dpc_associate->add_points(seg_dpc_associate->get_points()); + } + } + } + return *this; } + void Shower::add_segment(SegmentPtr seg, bool flag_include_vertices, const std::string& cloud_name_fit, const std::string& cloud_name_associate) + { + if (! seg->descriptor_valid()) { + return; + } + this->add_segment(seg); + + // If flag_include_vertices is true, add all vertices connected to this segment + if (flag_include_vertices) { + // Get the two vertices connected to this segment from the full graph + auto vertices = find_vertices(m_full_graph, seg); + + // Add each vertex to the view (skip the start_vertex) + if (vertices.first ) { + this->add_vertex(vertices.first); + } + if (vertices.second ) { + this->add_vertex(vertices.second); + } + } + + // Merge dynamic point clouds from segment to shower with the provided names + if (!cloud_name_fit.empty()) { + auto seg_dpc_fit = seg->dpcloud(cloud_name_fit); + if (seg_dpc_fit) { + auto shower_dpc_fit = this->dpcloud(cloud_name_fit); + if (!shower_dpc_fit) { + // Create new DPC if it doesn't exist in shower, copying wpid_params from segment's DPC + // We need the wpid_params to construct a new DPC, but it's private + // For now, just share the pointer - we'll need to modify this if independent DPCs are needed + this->dpcloud(cloud_name_fit, seg_dpc_fit); + } else { + // Add points from segment's DPC to existing shower's DPC + shower_dpc_fit->add_points(seg_dpc_fit->get_points()); + } + } + } + + if (!cloud_name_associate.empty()) { + auto seg_dpc_associate = seg->dpcloud(cloud_name_associate); + if (seg_dpc_associate) { + auto shower_dpc_associate = this->dpcloud(cloud_name_associate); + if (!shower_dpc_associate) { + // Create new DPC if it doesn't exist in shower + this->dpcloud(cloud_name_associate, seg_dpc_associate); + } else { + // Add points from segment's DPC to existing shower's DPC + shower_dpc_associate->add_points(seg_dpc_associate->get_points()); + } + } + } + } + + void Shower::set_flag_kinematics(bool val){ + if (val) { + set_flags(ShowerFlags::kKinematics); + } else { + unset_flags(ShowerFlags::kKinematics); + } + } + + bool Shower::get_flag_kinematics(){ + return flags_any(ShowerFlags::kKinematics); + } + + bool Shower::get_flag_shower(){ + return flags_any(ShowerFlags::kShower); + } + + void Shower::add_shower(Shower& shower, const std::string& cloud_name_fit, const std::string& cloud_name_associate){ + // Iterate through all vertices in the input shower's view using the nodes() accessor + for (auto vdesc : shower.nodes()) { + VertexPtr vtx = m_full_graph[vdesc].vertex; + if (vtx && vtx->descriptor_valid()) { + this->add_vertex(vtx); + } + } + + // Iterate through all segments in the input shower's view using the edges() accessor + for (auto edesc : shower.edges()) { + SegmentPtr seg = m_full_graph[edesc].segment; + if (seg && seg->descriptor_valid()) { + this->add_segment(seg); + + // Merge point clouds from this segment + // Handle "fit" point cloud + if (!cloud_name_fit.empty()) { + auto seg_dpc_fit = seg->dpcloud(cloud_name_fit); + if (seg_dpc_fit) { + auto shower_dpc_fit = this->dpcloud(cloud_name_fit); + if (!shower_dpc_fit) { + this->dpcloud(cloud_name_fit, seg_dpc_fit); + } else { + shower_dpc_fit->add_points(seg_dpc_fit->get_points()); + } + } + } + + // Handle "associate_points" point cloud + if (!cloud_name_associate.empty()) { + auto seg_dpc_associate = seg->dpcloud(cloud_name_associate); + if (seg_dpc_associate) { + auto shower_dpc_associate = this->dpcloud(cloud_name_associate); + if (!shower_dpc_associate) { + this->dpcloud(cloud_name_associate, seg_dpc_associate); + } else { + shower_dpc_associate->add_points(seg_dpc_associate->get_points()); + } + } + } + } + } + } + + void Shower::complete_structure_with_start_segment(std::set& used_segments, const std::string& cloud_name_fit, const std::string& cloud_name_associate) { + if (!m_start_segment || !m_start_segment->descriptor_valid()) return; + + std::vector new_segments; + std::vector new_vertices; + + // Add start_segment to the view and mark as used + used_segments.insert(m_start_segment); + + + // Find vertices connected to start_segment (excluding start_vertex) + auto vertices = find_vertices(m_full_graph, m_start_segment); + if (vertices.first && vertices.first != m_start_vertex) { + this->add_vertex(vertices.first); + new_vertices.push_back(vertices.first); + } + if (vertices.second && vertices.second != m_start_vertex) { + this->add_vertex(vertices.second); + new_vertices.push_back(vertices.second); + } + + // Worklist algorithm: explore connected segments and vertices + while (!new_vertices.empty() || !new_segments.empty()) { + // Process new vertices - find all segments connected to them + if (!new_vertices.empty()) { + VertexPtr vtx = new_vertices.back(); + new_vertices.pop_back(); + + // Find all segments connected to this vertex + if (vtx->descriptor_valid()) { + auto vdesc = vtx->get_descriptor(); + for (auto edesc : boost::make_iterator_range(boost::out_edges(vdesc, m_full_graph))) { + SegmentPtr seg = m_full_graph[edesc].segment; + if (seg && seg->descriptor_valid() && used_segments.find(seg) == used_segments.end()) { + this->add_segment(seg); + new_segments.push_back(seg); + used_segments.insert(seg); + + // Merge point clouds from this segment + if (!cloud_name_fit.empty()) { + auto seg_dpc_fit = seg->dpcloud(cloud_name_fit); + if (seg_dpc_fit) { + auto shower_dpc_fit = this->dpcloud(cloud_name_fit); + if (!shower_dpc_fit) { + this->dpcloud(cloud_name_fit, seg_dpc_fit); + } else { + shower_dpc_fit->add_points(seg_dpc_fit->get_points()); + } + } + } + + if (!cloud_name_associate.empty()) { + auto seg_dpc_associate = seg->dpcloud(cloud_name_associate); + if (seg_dpc_associate) { + auto shower_dpc_associate = this->dpcloud(cloud_name_associate); + if (!shower_dpc_associate) { + this->dpcloud(cloud_name_associate, seg_dpc_associate); + } else { + shower_dpc_associate->add_points(seg_dpc_associate->get_points()); + } + } + } + } + } + } + } + + // Process new segments - find all vertices connected to them + if (!new_segments.empty()) { + SegmentPtr seg = new_segments.back(); + new_segments.pop_back(); + + // Find vertices connected to this segment (excluding start_vertex) + auto vertices = find_vertices(m_full_graph, seg); + if (vertices.first && vertices.first != m_start_vertex) { + if (!this->has_node(vertices.first->get_descriptor())) { + this->add_vertex(vertices.first); + new_vertices.push_back(vertices.first); + } + } + if (vertices.second && vertices.second != m_start_vertex) { + if (!this->has_node(vertices.second->get_descriptor())) { + this->add_vertex(vertices.second); + new_vertices.push_back(vertices.second); + } + } + } + } + } -} +} \ No newline at end of file diff --git a/clus/src/PRShowerFunctions.cxx b/clus/src/PRShowerFunctions.cxx new file mode 100644 index 00000000..95766f35 --- /dev/null +++ b/clus/src/PRShowerFunctions.cxx @@ -0,0 +1,2 @@ +#include "WireCellClus/PRShower.h" + From d5ee192d954c3bb59aa325b6986fa040b231a954 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sun, 4 Jan 2026 08:05:35 -0800 Subject: [PATCH 088/111] add more functions --- clus/inc/WireCellClus/PRShower.h | 4 +++ clus/src/PRShower.cxx | 61 ++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/clus/inc/WireCellClus/PRShower.h b/clus/inc/WireCellClus/PRShower.h index f81cf8d6..d2bf5469 100644 --- a/clus/inc/WireCellClus/PRShower.h +++ b/clus/inc/WireCellClus/PRShower.h @@ -153,6 +153,10 @@ namespace WireCell::Clus::PR { void add_shower(Shower& temp_shower, const std::string& cloud_name_fit = "fit", const std::string& cloud_name_associate = "associate_points"); void complete_structure_with_start_segment(std::set& used_segments, const std::string& cloud_name_fit = "fit", const std::string& cloud_name_associate = "associate_points"); + + // get the information from the shower + void fill_sets(std::set& used_vertices, std::set& used_segments, bool flag_exclude_start_segment = true); + void fill_point_vector(std::vector& points, bool flag_main = true); private: diff --git a/clus/src/PRShower.cxx b/clus/src/PRShower.cxx index 0d6a4ba6..486df112 100644 --- a/clus/src/PRShower.cxx +++ b/clus/src/PRShower.cxx @@ -328,5 +328,66 @@ namespace WireCell::Clus::PR { } } + void Shower::fill_sets(std::set& used_vertices, std::set& used_segments, bool flag_exclude_start_segment){ + // Fill used_vertices with all vertices in this shower's view + for (auto vdesc : this->nodes()) { + VertexPtr vtx = m_full_graph[vdesc].vertex; + if (vtx) { + used_vertices.insert(vtx); + } + } + + // Fill used_segments with all segments in this shower's view + for (auto edesc : this->edges()) { + SegmentPtr seg = m_full_graph[edesc].segment; + if (seg) { + // Skip start_segment if flag is set + if (flag_exclude_start_segment && seg == m_start_segment) { + continue; + } + used_segments.insert(seg); + } + } + } + + void Shower::fill_point_vector(std::vector& points, bool flag_main){ + // Get the main cluster ID if flag_main is true + const Facade::Cluster* main_cluster = nullptr; + if (flag_main && m_start_segment && m_start_segment->cluster()) { + main_cluster = m_start_segment->cluster(); + } + + // Fill points from all segments in the shower's view + for (auto edesc : this->edges()) { + SegmentPtr seg = m_full_graph[edesc].segment; + if (seg) { + // Skip if flag_main is set and segment is not in the main cluster + if (flag_main && main_cluster && seg->cluster() != main_cluster) { + continue; + } + + // Get segment fit points and add all except first and last + const auto& fits = seg->fits(); + for (size_t i = 1; i + 1 < fits.size(); i++) { + points.push_back(fits[i].point); + } + } + } + + // Fill points from all vertices in the shower's view + for (auto vdesc : this->nodes()) { + VertexPtr vtx = m_full_graph[vdesc].vertex; + if (vtx) { + // Skip if flag_main is set and vertex is not in the main cluster + if (flag_main && main_cluster && vtx->cluster() != main_cluster) { + continue; + } + + // Add the vertex fit point + points.push_back(vtx->fit().point); + } + } + } + } \ No newline at end of file From f505bb8867b42185e72493a7db811d16166c3b33 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sun, 4 Jan 2026 09:12:00 -0800 Subject: [PATCH 089/111] continue working on the PRShower --- clus/inc/WireCellClus/PRShower.h | 6 +- clus/src/PRShower.cxx | 121 +++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+), 1 deletion(-) diff --git a/clus/inc/WireCellClus/PRShower.h b/clus/inc/WireCellClus/PRShower.h index d2bf5469..7b923adf 100644 --- a/clus/inc/WireCellClus/PRShower.h +++ b/clus/inc/WireCellClus/PRShower.h @@ -157,7 +157,11 @@ namespace WireCell::Clus::PR { // get the information from the shower void fill_sets(std::set& used_vertices, std::set& used_segments, bool flag_exclude_start_segment = true); void fill_point_vector(std::vector& points, bool flag_main = true); - + TrajectoryView& fill_maps(); + + std::pair, std::set> get_connected_pieces(SegmentPtr seg); + + std::pair get_last_segment_vertex_long_muon(std::set& segments_in_muons); private: diff --git a/clus/src/PRShower.cxx b/clus/src/PRShower.cxx index 486df112..18feaf3a 100644 --- a/clus/src/PRShower.cxx +++ b/clus/src/PRShower.cxx @@ -389,5 +389,126 @@ namespace WireCell::Clus::PR { } } + TrajectoryView& Shower::fill_maps() { + return *this; + } + + std::pair, std::set> Shower::get_connected_pieces(SegmentPtr seg){ + std::set used_segments; + std::set used_vertices; + + // Check if the segment is valid and in the view + if (!seg || !seg->descriptor_valid() || !this->has_edge(seg->get_descriptor())) { + return std::make_pair(used_vertices, used_segments); + } + + std::vector new_segments; + std::vector new_vertices; + + // Start with the given segment + new_segments.push_back(seg); + used_segments.insert(seg); + + // Worklist algorithm: explore connected segments and vertices in the view + while (!new_vertices.empty() || !new_segments.empty()) { + // Process new vertices - find all segments connected to them in the view + if (!new_vertices.empty()) { + VertexPtr vtx = new_vertices.back(); + new_vertices.pop_back(); + + // Find all segments connected to this vertex in the full graph, then check if in view + if (vtx->descriptor_valid()) { + auto vdesc = vtx->get_descriptor(); + for (auto edesc : boost::make_iterator_range(boost::out_edges(vdesc, m_full_graph))) { + // Check if this edge is in the view + if (this->has_edge(edesc)) { + SegmentPtr seg1 = m_full_graph[edesc].segment; + if (seg1 && used_segments.find(seg1) == used_segments.end()) { + new_segments.push_back(seg1); + used_segments.insert(seg1); + } + } + } + } + } + + // Process new segments - find all vertices connected to them in the view + if (!new_segments.empty()) { + SegmentPtr seg1 = new_segments.back(); + new_segments.pop_back(); + + // Find vertices connected to this segment in the full graph + auto vertices = find_vertices(m_full_graph, seg1); + + // Check if vertices are in the view and not yet visited + if (vertices.first && this->has_node(vertices.first->get_descriptor()) + && used_vertices.find(vertices.first) == used_vertices.end()) { + new_vertices.push_back(vertices.first); + used_vertices.insert(vertices.first); + } + if (vertices.second && this->has_node(vertices.second->get_descriptor()) + && used_vertices.find(vertices.second) == used_vertices.end()) { + new_vertices.push_back(vertices.second); + used_vertices.insert(vertices.second); + } + } + } + + return std::make_pair(used_vertices, used_segments); + } + + std::pair Shower::get_last_segment_vertex_long_muon(std::set& segments_in_muons) { + VertexPtr s_vtx = m_start_vertex; + SegmentPtr s_seg = m_start_segment; + + if (!s_vtx || !s_seg) { + return std::make_pair(s_seg, s_vtx); + } + + std::set used_segments; + used_segments.insert(s_seg); + + bool flag_continue = true; + while (flag_continue) { + flag_continue = false; + + // If current vertex is start_vertex, continue + if (s_vtx == m_start_vertex) { + flag_continue = true; + } else { + // Look for a new segment connected to s_vtx that is in segments_in_muons and not used + if (s_vtx->descriptor_valid()) { + auto vdesc = s_vtx->get_descriptor(); + // Iterate over segments connected to this vertex in the full graph, then check if in view + for (auto edesc : boost::make_iterator_range(boost::out_edges(vdesc, m_full_graph))) { + // Check if this edge is in the view + if (this->has_edge(edesc)) { + SegmentPtr sg = m_full_graph[edesc].segment; + if (sg && segments_in_muons.find(sg) != segments_in_muons.end() + && used_segments.find(sg) == used_segments.end()) { + s_seg = sg; + used_segments.insert(s_seg); + flag_continue = true; + break; + } + } + } + } + } + + // If we found a new segment, find the other vertex connected to it + if (flag_continue) { + auto vertices = find_vertices(m_full_graph, s_seg); + if (vertices.first && vertices.first != s_vtx) { + s_vtx = vertices.first; + } else if (vertices.second && vertices.second != s_vtx) { + s_vtx = vertices.second; + } + } + } + + return std::make_pair(s_seg, s_vtx); + } + } \ No newline at end of file From 0b125685a7bbcdc409a38219380c4f6c38713c0e Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sun, 4 Jan 2026 11:14:47 -0800 Subject: [PATCH 090/111] update code --- clus/inc/WireCellClus/PRShowerFunctions.h | 16 +- clus/src/PRShowerFunctions.cxx | 180 +++++++++++++++++++++- 2 files changed, 191 insertions(+), 5 deletions(-) diff --git a/clus/inc/WireCellClus/PRShowerFunctions.h b/clus/inc/WireCellClus/PRShowerFunctions.h index ea5a4432..85988342 100644 --- a/clus/inc/WireCellClus/PRShowerFunctions.h +++ b/clus/inc/WireCellClus/PRShowerFunctions.h @@ -1,6 +1,10 @@ #ifndef WIRECELL_CLUS_PR_SHOWER_FUNCTIONS #define WIRECELL_CLUS_PR_SHOWER_FUNCTIONS +#include "WireCellClus/PRShower.h" +#include "WireCellUtil/Units.h" + + namespace WireCell::Clus::PR { /** Modify shower assuming shower kinematics. @@ -8,11 +12,15 @@ namespace WireCell::Clus::PR { * This free function is is equivalent to the method of WCP's * WCShower::calculate_kinematics(). */ - void shower_kinematics(ShowerPtr shower); - void update_particle_type(ShowerPtr shower); - - void longmuon_kinematics(ShowerPtr shower); + std::pair shower_get_closest_point(Shower& shower, const WireCell::Point& point, const std::string& cloud_name = "fit"); + + double shower_get_closest_dis(Shower& shower, SegmentPtr seg, const std::string& cloud_name = "fit"); + + double shower_get_dis(Shower& shower, SegmentPtr seg, const std::string& cloud_name = "fit"); + + WireCell::Vector shower_cal_dir_3vector(Shower& shower, const WireCell::Point& p, double dis_cut = 15*units::cm); + } #endif diff --git a/clus/src/PRShowerFunctions.cxx b/clus/src/PRShowerFunctions.cxx index 95766f35..00e4d0e9 100644 --- a/clus/src/PRShowerFunctions.cxx +++ b/clus/src/PRShowerFunctions.cxx @@ -1,2 +1,180 @@ -#include "WireCellClus/PRShower.h" +#include "WireCellClus/PRShowerFunctions.h" +#include "WireCellClus/PRSegmentFunctions.h" +#include "WireCellClus/DynamicPointCloud.h" +using namespace WireCell::Clus::PR; + +namespace WireCell::Clus::PR { + + std::pair shower_get_closest_point(Shower& shower, const WireCell::Point& point, const std::string& cloud_name /* = "fit" */){ + // Get the dynamic point cloud from the shower + auto pcloud = shower.get_pcloud(cloud_name); + + // If no point cloud exists, return invalid result + if (!pcloud) { + return std::make_pair(-1.0, WireCell::Point(0, 0, 0)); + } + + // Get the 3D KD-tree + auto& kd3d = pcloud->kd3d(); + + // Prepare query point + std::vector query = {point.x(), point.y(), point.z()}; + + // Find the nearest neighbor + auto results = kd3d.knn(1, query); + + // Check if we found a point + if (results.empty()) { + return std::make_pair(-1.0, WireCell::Point(0, 0, 0)); + } + + // Get the result + const size_t idx = results[0].first; + const double distance = sqrt(results[0].second); // KD-tree returns squared distance + + // Get the actual point from the point cloud + const auto& points = pcloud->get_points(); + const auto& closest_pt = points[idx]; + + return std::make_pair(distance, WireCell::Point(closest_pt.x, closest_pt.y, closest_pt.z)); + } + + double shower_get_closest_dis(Shower& shower, SegmentPtr seg, const std::string& cloud_name /* = "fit" */){ + // Get the first point from segment's DynamicPointCloud + auto seg_dpc = seg->dpcloud(cloud_name); + if (!seg_dpc) { + return -1.0; + } + + const auto& seg_points = seg_dpc->get_points(); + if (seg_points.empty()) { + return -1.0; + } + + // Use the first point from segment's point cloud + WireCell::Point first_point(seg_points.front().x, seg_points.front().y, seg_points.front().z); + + // Step 1: Get closest point in shower to the first point of the segment + WireCell::Point test_p = shower_get_closest_point(shower, first_point, cloud_name).second; + + // Step 2: Get closest point in segment to that point (uses segment's DPC with KD-tree) + test_p = segment_get_closest_point(seg, test_p, cloud_name).second; + + // Step 3: Get closest point in shower to that point + test_p = shower_get_closest_point(shower, test_p, cloud_name).second; + + // Step 4: Get closest distance in segment to that point + return segment_get_closest_point(seg, test_p, cloud_name).first; + } + + double shower_get_dis(Shower& shower, SegmentPtr seg, const std::string& cloud_name /* = "fit" */){ + double min_dis = 1e9; + WireCell::Point min_point; + + // Get the first point from the input segment's DynamicPointCloud + auto seg_dpc = seg->dpcloud(cloud_name); + if (!seg_dpc) { + return -1.0; + } + + const auto& seg_points = seg_dpc->get_points(); + if (seg_points.empty()) { + return -1.0; + } + + WireCell::Point test_p(seg_points.front().x, seg_points.front().y, seg_points.front().z); + + // Get the view graph to access segments + const auto& view = shower.view_graph(); + + // First iteration: find the closest point in any shower segment to test_p + for (auto edesc : shower.edges()) { + SegmentPtr sg = view[edesc].segment; + if (!sg) continue; + + auto results = segment_get_closest_point(sg, test_p, cloud_name); + if (results.first < min_dis) { + min_dis = results.first; + min_point = results.second; + } + } + + // Get closest point in input segment to that minimum point + auto results1 = segment_get_closest_point(seg, min_point, cloud_name); + test_p = results1.second; + + // Second iteration: find the closest distance from any shower segment to the new test point + for (auto edesc : shower.edges()) { + SegmentPtr sg = view[edesc].segment; + if (!sg) continue; + + auto results = segment_get_closest_point(sg, test_p, cloud_name); + if (results.first < min_dis) { + min_dis = results.first; + min_point = results.second; + } + } + + return min_dis; + } + + WireCell::Vector shower_cal_dir_3vector(Shower& shower, const WireCell::Point& p, double dis_cut /* = 15*units::cm */){ + WireCell::Point p_sum(0, 0, 0); + int ncount = 0; + + // Get the view graph to access segments + const auto& view = shower.view_graph(); + + // Loop through all segments in the shower + for (auto edesc : shower.edges()) { + SegmentPtr seg = view[edesc].segment; + if (!seg) continue; + + // Get the segment's fits + const auto& fits = seg->fits(); + + // Check each fit point in the segment + for (const auto& fit : fits) { + // Calculate distance from this fit point to p + double dis = sqrt(pow(fit.point.x() - p.x(), 2) + + pow(fit.point.y() - p.y(), 2) + + pow(fit.point.z() - p.z(), 2)); + + // If within distance cutoff, accumulate the point + if (dis < dis_cut) { + p_sum = WireCell::Point(p_sum.x() + fit.point.x(), + p_sum.y() + fit.point.y(), + p_sum.z() + fit.point.z()); + ncount++; + } + } + } + + // If no points were found, return zero vector + if (ncount == 0) { + return WireCell::Vector(0, 0, 0); + } + + // Calculate the average point and direction vector + WireCell::Point p_avg(p_sum.x() / ncount, + p_sum.y() / ncount, + p_sum.z() / ncount); + + // Direction vector from p to average point + WireCell::Vector dir(p_avg.x() - p.x(), + p_avg.y() - p.y(), + p_avg.z() - p.z()); + + // Normalize the vector + double magnitude = dir.magnitude(); + if (magnitude > 0) { + dir = dir.norm(); + } + + return dir; + } + + + +} // namespace WireCell::Clus::PR From e9de71912aeafc8cb5530510940bf8ebffbc88ce Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sun, 4 Jan 2026 12:25:51 -0800 Subject: [PATCH 091/111] fully implement the PRShower --- clus/inc/WireCellClus/PRShower.h | 18 + clus/src/PRShower.cxx | 623 +++++++++++++++++++++++++++++++ 2 files changed, 641 insertions(+) diff --git a/clus/inc/WireCellClus/PRShower.h b/clus/inc/WireCellClus/PRShower.h index 7b923adf..e3496a21 100644 --- a/clus/inc/WireCellClus/PRShower.h +++ b/clus/inc/WireCellClus/PRShower.h @@ -7,6 +7,9 @@ #include "WireCellUtil/Flagged.h" #include "WireCellUtil/Point.h" +#include "WireCellIface/IRecombinationModel.h" +#include "WireCellClus/ParticleDataSet.h" + namespace WireCell::Clus::PR { /** The "flags" that may be set on a shower. @@ -163,6 +166,21 @@ namespace WireCell::Clus::PR { std::pair get_last_segment_vertex_long_muon(std::set& segments_in_muons); + // some simple get functions + int get_num_main_segments(); + int get_num_segments(); + + double get_total_length(); + double get_total_length(Facade::Cluster* cluster); + double get_total_track_length(); + + std::vector get_stem_dQ_dx(VertexPtr vertex, SegmentPtr segment, int limit = 20); + + // calculate the kinematics + void update_particle_type(const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); + void calculate_kinematics(const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); + void calculate_kinematics_long_muon(std::set& segments_in_muons, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); + private: Graph& m_full_graph; diff --git a/clus/src/PRShower.cxx b/clus/src/PRShower.cxx index 18feaf3a..483d13f4 100644 --- a/clus/src/PRShower.cxx +++ b/clus/src/PRShower.cxx @@ -1,5 +1,7 @@ #include "WireCellClus/PRShower.h" #include "WireCellClus/PRGraph.h" +#include "WireCellClus/PRSegmentFunctions.h" +#include "WireCellClus/PRShowerFunctions.h" #include "WireCellClus/DynamicPointCloud.h" namespace WireCell::Clus::PR { @@ -510,5 +512,626 @@ namespace WireCell::Clus::PR { return std::make_pair(s_seg, s_vtx); } + int Shower::get_num_main_segments() { + int num = 0; + + // If no start segment, return 0 + if (!m_start_segment) { + return 0; + } + + // Get the start segment's cluster + auto start_cluster = m_start_segment->cluster(); + if (!start_cluster) { + return 0; + } + + // Get the view graph to access segments + const auto& view = this->view_graph(); + + // Iterate through all segments in the shower + for (auto edesc : this->edges()) { + SegmentPtr seg = view[edesc].segment; + if (!seg) continue; + + // Check if this segment's cluster matches the start segment's cluster + if (seg->cluster() == start_cluster) { + num++; + } + } + + return num; + } + + int Shower::get_num_segments() { + return this->edges().size(); + } + + double Shower::get_total_length(){ + double total_length = 0; + + // Get the view graph to access segments + const auto& view = this->view_graph(); + + // Iterate through all segments in the shower + for (auto edesc : this->edges()) { + SegmentPtr seg = view[edesc].segment; + if (!seg) continue; + + // Add segment length + total_length += segment_track_length(seg); + } + + return total_length; + } + double Shower::get_total_length(Facade::Cluster* cluster){ + double total_length = 0; + + if (!cluster) { + return 0; + } + + // Get the view graph to access segments + const auto& view = this->view_graph(); + + // Iterate through all segments in the shower + for (auto edesc : this->edges()) { + SegmentPtr seg = view[edesc].segment; + if (!seg) continue; + + // Check if segment's cluster matches the input cluster + if (seg->cluster() == cluster) { + total_length += segment_track_length(seg); + } + } + + return total_length; + } + double Shower::get_total_track_length(){ + double total_length = 0; + + // Get the view graph to access segments + const auto& view = this->view_graph(); + + // Iterate through all segments in the shower + for (auto edesc : this->edges()) { + SegmentPtr seg = view[edesc].segment; + if (!seg) continue; + + // Only count segments that are NOT shower segments + // Check if segment has shower flags (kShowerTrajectory or kShowerTopology) + if (!seg->flags_any(SegmentFlags::kShowerTrajectory) && + !seg->flags_any(SegmentFlags::kShowerTopology)) { + total_length += segment_track_length(seg); + } + } + + return total_length; + } + + void Shower::update_particle_type(const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model){ + double track_length = 0; + double shower_length = 0; + + // Only process if there's more than one segment + if (this->edges().size() <= 1) { + return; + } + + // Get the view graph to access segments + const auto& view = this->view_graph(); + + // Iterate through all segments in the shower + for (auto edesc : this->edges()) { + SegmentPtr seg = view[edesc].segment; + if (!seg) continue; + + double length = segment_track_length(seg); + + // Check if segment is a shower segment OR not a proton (PDG 2212) + bool is_shower = seg->flags_any(SegmentFlags::kShowerTrajectory) || + seg->flags_any(SegmentFlags::kShowerTopology); + + bool is_not_proton = true; + if (seg->has_particle_info()) { + int pdg = seg->particle_info()->pdg(); + is_not_proton = (std::abs(pdg) != 2212); + } + + if (is_shower || is_not_proton) { + shower_length += length; + } else { + track_length += length; + } + } + + // If shower_length dominates, update start_segment to electron + if (shower_length > track_length && m_start_segment) { + // Calculate 4-momentum for electron (PDG = 11) + auto four_momentum = segment_cal_4mom(m_start_segment, 11, particle_data, recomb_model); + + // Create ParticleInfo for electron + auto pinfo = std::make_shared( + 11, // electron PDG + particle_data->get_particle_mass(11), // electron mass + particle_data->pdg_to_name(11), // "electron" + four_momentum // 4-momentum + ); + + // Store particle info in start_segment + m_start_segment->particle_info(pinfo); + } + } + + std::vector Shower::get_stem_dQ_dx(VertexPtr vertex, SegmentPtr segment, int limit /*=20*/){ + std::vector vec_dQ_dx; + const double MIP_dQdx = 43e3 / units::cm; + + if (!vertex || !segment) { + return vec_dQ_dx; + } + + // Get dQ and dx from segment's fits + const auto& fits = segment->fits(); + if (fits.empty()) { + return vec_dQ_dx; + } + + // Determine direction based on vertex position relative to segment + // Check if vertex is at the front of the segment + bool vertex_at_front = false; + if (!segment->wcpts().empty()) { + double d1 = WireCell::ray_length(WireCell::Ray{vertex->wcpt().point, segment->wcpts().front().point}); + double d2 = WireCell::ray_length(WireCell::Ray{vertex->wcpt().point, segment->wcpts().back().point}); + vertex_at_front = (d1 < d2); + } + + // Fill vec_dQ_dx based on direction + if (vertex_at_front) { + for (size_t i = 0; i < fits.size(); i++) { + double dQ_dx_normalized = fits[i].dQ / (fits[i].dx + 1e-9) / MIP_dQdx; + vec_dQ_dx.push_back(dQ_dx_normalized); + if (vec_dQ_dx.size() >= (size_t)limit) break; + } + } else { + for (int i = (int)fits.size() - 1; i >= 0; i--) { + double dQ_dx_normalized = fits[i].dQ / (fits[i].dx + 1e-9) / MIP_dQdx; + vec_dQ_dx.push_back(dQ_dx_normalized); + if (vec_dQ_dx.size() >= (size_t)limit) break; + } + } + + // If this is the start_segment and we don't have enough points, continue to next segments + if (segment == m_start_segment && vec_dQ_dx.size() < (size_t)limit) { + VertexPtr curr_vertex = vertex; + SegmentPtr curr_segment = segment; + int count = 0; + + while (vec_dQ_dx.size() < (size_t)limit && count < 3) { + // Find next vertex (the other end of current segment) + VertexPtr next_vertex = find_other_vertex(m_full_graph, curr_segment, curr_vertex); + if (!next_vertex) break; + + // Direction from current vertex to next vertex + WireCell::Vector dir1 = curr_vertex->fit().point - next_vertex->fit().point; + + // Find the next segment with largest angle + SegmentPtr next_segment = nullptr; + WireCell::Vector dir2; + double max_angle = 0; + + auto next_vdesc = next_vertex->get_descriptor(); + for (auto edesc : boost::make_iterator_range(boost::out_edges(next_vdesc, m_full_graph))) { + if (!has_edge(edesc)) continue; // Only consider edges in this view + + SegmentPtr seg = m_full_graph[edesc].segment; + if (seg == curr_segment) continue; + + WireCell::Vector tmp_dir = segment_cal_dir_3vector(seg, next_vertex->fit().point, 10 * units::cm); + double angle = std::acos(std::clamp(dir1.dot(tmp_dir) / (dir1.magnitude() * tmp_dir.magnitude() + 1e-9), -1.0, 1.0)) * 180.0 / M_PI; + + if (angle > max_angle) { + max_angle = angle; + next_segment = seg; + dir2 = tmp_dir; + } + } + + if (!next_segment) break; + + // Check if there are other segments that would make this "bad" + bool flag_bad = false; + for (auto edesc : boost::make_iterator_range(boost::out_edges(next_vdesc, m_full_graph))) { + if (!has_edge(edesc)) continue; + + SegmentPtr seg = m_full_graph[edesc].segment; + if (seg == curr_segment || seg == next_segment) continue; + + double seg_length = segment_track_length(seg); + if (seg_length > 3 * units::cm) { + WireCell::Vector tmp_dir = segment_cal_dir_3vector(seg, next_vertex->fit().point, 10 * units::cm); + double angle = std::acos(std::clamp(dir2.dot(tmp_dir) / (dir2.magnitude() * tmp_dir.magnitude() + 1e-9), -1.0, 1.0)) * 180.0 / M_PI; + if (angle < 25) { + flag_bad = true; + break; + } + } + } + + if (flag_bad) break; + + // Remove last element and add points from next segment + if (!vec_dQ_dx.empty()) { + vec_dQ_dx.pop_back(); + } + + const auto& next_fits = next_segment->fits(); + if (next_fits.empty()) break; + + // Determine direction for next segment + bool next_vertex_at_front = false; + if (!next_segment->wcpts().empty()) { + double d1 = WireCell::ray_length(WireCell::Ray{next_vertex->wcpt().point, next_segment->wcpts().front().point}); + double d2 = WireCell::ray_length(WireCell::Ray{next_vertex->wcpt().point, next_segment->wcpts().back().point}); + next_vertex_at_front = (d1 < d2); + } + + // Add dQ/dx from next segment + if (next_vertex_at_front) { + for (size_t i = 0; i < next_fits.size(); i++) { + double dQ_dx_normalized = next_fits[i].dQ / (next_fits[i].dx + 1e-9) / MIP_dQdx; + vec_dQ_dx.push_back(dQ_dx_normalized); + if (vec_dQ_dx.size() >= (size_t)limit) break; + } + } else { + for (int i = (int)next_fits.size() - 1; i >= 0; i--) { + double dQ_dx_normalized = next_fits[i].dQ / (next_fits[i].dx + 1e-9) / MIP_dQdx; + vec_dQ_dx.push_back(dQ_dx_normalized); + if (vec_dQ_dx.size() >= (size_t)limit) break; + } + } + + if (vec_dQ_dx.size() >= (size_t)limit) break; + + // Prepare for next iteration + curr_vertex = next_vertex; + curr_segment = next_segment; + count++; + } + } + + return vec_dQ_dx; + } + + void Shower::calculate_kinematics(const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model){ + int nsegments = this->edges().size(); + + if (nsegments == 1) { + // Single segment case + if (!m_start_segment) return; + + // Set particle type from start segment + if (m_start_segment->has_particle_info()) { + data.particle_type = m_start_segment->particle_info()->pdg(); + } + + // Check if shower + bool flag_shower = m_start_segment->flags_any(SegmentFlags::kShowerTrajectory) || + m_start_segment->flags_any(SegmentFlags::kShowerTopology); + + // Calculate energies + double seg_length = segment_track_length(m_start_segment); + data.kenergy_range = cal_kine_range(seg_length, data.particle_type, particle_data); + data.kenergy_dQdx = segment_cal_kine_dQdx(m_start_segment, recomb_model); + + // Calculate kenergy_best + if (data.start_connection_type == 1) { + data.kenergy_best = (seg_length < 4 * units::cm) ? data.kenergy_dQdx : data.kenergy_range; + } else { + if (flag_shower) { + data.kenergy_best = 0; + } else { + data.kenergy_best = (seg_length < 4 * units::cm) ? data.kenergy_dQdx : data.kenergy_range; + } + } + + // Calculate start_point and end_point + const auto& fits = m_start_segment->fits(); + if (data.start_connection_type == 1 || !this->dpcloud("fit")) { + if (!fits.empty()) { + if (m_start_segment->dirsign() == 1) { + data.start_point = fits.front().point; + data.end_point = fits.back().point; + } else if (m_start_segment->dirsign() == -1) { + data.start_point = fits.back().point; + data.end_point = fits.front().point; + } + } + } else { + if (m_start_vertex) { + data.start_point = shower_get_closest_point(*this, m_start_vertex->fit().point, "fit").second; + + // Find farthest vertex + double max_dis = 0; + const auto& view = this->view_graph(); + for (auto vdesc : this->nodes()) { + VertexPtr vtx = view[vdesc].vertex; + if (!vtx) continue; + double dis = (data.start_point - vtx->fit().point).magnitude(); + if (dis > max_dis) { + max_dis = dis; + data.end_point = vtx->fit().point; + } + } + } + } + + // Calculate init_dir + if (data.start_connection_type == 1) { + data.init_dir = segment_cal_dir_3vector(m_start_segment); + } else if (data.start_connection_type == 2 || data.start_connection_type == 3) { + if (m_start_vertex) { + data.init_dir = (data.start_point - m_start_vertex->fit().point).norm(); + } + } + + } else { + // Multiple segments case + if (!m_start_segment) return; + + // Get number of connected segments + auto [segs, verts] = get_connected_pieces(m_start_segment); + int nconnected_segs = segs.size(); + + // Set particle type + if (m_start_segment->has_particle_info()) { + data.particle_type = m_start_segment->particle_info()->pdg(); + } + + bool flag_shower = m_start_segment->flags_any(SegmentFlags::kShowerTrajectory) || + m_start_segment->flags_any(SegmentFlags::kShowerTopology); + + if (nsegments == nconnected_segs) { + // Single track (all connected) + + // Calculate start_point + const auto& fits = m_start_segment->fits(); + if (data.start_connection_type == 1 || !this->dpcloud("fit")) { + if (!fits.empty()) { + if (m_start_segment->dirsign() == 1) { + data.start_point = fits.front().point; + } else if (m_start_segment->dirsign() == -1) { + data.start_point = fits.back().point; + } + } + } else { + if (m_start_vertex) { + data.start_point = shower_get_closest_point(*this, m_start_vertex->fit().point, "fit").second; + } + } + + // Calculate init_dir + double seg_length = segment_track_length(m_start_segment); + if (data.start_connection_type == 1) { + if (seg_length > 8 * units::cm) { + data.init_dir = segment_cal_dir_3vector(m_start_segment); + } else if (m_start_vertex) { + data.init_dir = shower_cal_dir_3vector(*this, m_start_vertex->fit().point, 12 * units::cm); + } + } else if (data.start_connection_type == 2 || data.start_connection_type == 3) { + if (m_start_vertex) { + data.init_dir = (data.start_point - m_start_vertex->fit().point).norm(); + } + } + + // Find farthest vertex for end_point + double max_dis = 0; + const auto& view = this->view_graph(); + for (auto vdesc : this->nodes()) { + VertexPtr vtx = view[vdesc].vertex; + if (!vtx) continue; + double dis = (data.start_point - vtx->fit().point).magnitude(); + if (dis > max_dis) { + max_dis = dis; + data.end_point = vtx->fit().point; + } + } + + // Collect all dQ and dx from all segments + double total_length = 0; + std::vector vec_dQ, vec_dx; + for (auto edesc : this->edges()) { + SegmentPtr seg = view[edesc].segment; + if (!seg) continue; + + total_length += segment_track_length(seg); + + const auto& seg_fits = seg->fits(); + for (const auto& fit : seg_fits) { + vec_dQ.push_back(fit.dQ); + vec_dx.push_back(fit.dx); + } + } + + // Calculate energies + data.kenergy_range = cal_kine_range(total_length, data.particle_type, particle_data); + data.kenergy_dQdx = cal_kine_dQdx(vec_dQ, vec_dx, recomb_model); + + // Calculate kenergy_best + if (data.start_connection_type == 1) { + data.kenergy_best = (seg_length < 4 * units::cm) ? data.kenergy_dQdx : data.kenergy_range; + } else { + if (flag_shower) { + data.kenergy_best = 0; + } else { + data.kenergy_best = (seg_length < 4 * units::cm) ? data.kenergy_dQdx : data.kenergy_range; + } + } + + } else { + // Multiple tracks (not all connected) + + // Calculate start_point + const auto& fits = m_start_segment->fits(); + if (data.start_connection_type == 1 || !this->dpcloud("fit")) { + if (!fits.empty()) { + if (m_start_segment->dirsign() == 1) { + data.start_point = fits.front().point; + } else if (m_start_segment->dirsign() == -1) { + data.start_point = fits.back().point; + } + } + } else { + if (m_start_vertex) { + data.start_point = shower_get_closest_point(*this, m_start_vertex->fit().point, "fit").second; + } + } + + // Calculate init_dir + double seg_length = segment_track_length(m_start_segment); + if (data.start_connection_type == 1) { + if (seg_length > 8 * units::cm) { + data.init_dir = segment_cal_dir_3vector(m_start_segment); + } else if (m_start_vertex) { + data.init_dir = shower_cal_dir_3vector(*this, m_start_vertex->fit().point, 12 * units::cm); + } + } else if (data.start_connection_type == 2 || data.start_connection_type == 3) { + if (m_start_vertex) { + data.init_dir = (data.start_point - m_start_vertex->fit().point).norm(); + } + } + + // Find farthest vertex for end_point + double max_dis = 0; + const auto& view = this->view_graph(); + for (auto vdesc : this->nodes()) { + VertexPtr vtx = view[vdesc].vertex; + if (!vtx) continue; + double dis = (data.start_point - vtx->fit().point).magnitude(); + if (dis > max_dis) { + max_dis = dis; + data.end_point = vtx->fit().point; + } + } + + // Collect all dQ and dx from all segments + std::vector vec_dQ, vec_dx; + for (auto edesc : this->edges()) { + SegmentPtr seg = view[edesc].segment; + if (!seg) continue; + + const auto& seg_fits = seg->fits(); + for (const auto& fit : seg_fits) { + vec_dQ.push_back(fit.dQ); + vec_dx.push_back(fit.dx); + } + } + + // Calculate energies + data.kenergy_range = 0; + data.kenergy_dQdx = cal_kine_dQdx(vec_dQ, vec_dx, recomb_model); + data.kenergy_best = 0; + } + } + } + + void Shower::calculate_kinematics_long_muon(std::set& segments_in_muons, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model){ + // Get particle type from start segment + int particle_type = abs(m_start_segment->particle_info()->pdg()); + double particle_mass = m_start_segment->particle_info()->mass(); + + unset_flags(ShowerFlags::kKinematics); + + // Calculate total length ONLY from segments in segments_in_muons + double total_length = 0; + for (auto edesc : this->edges()) { + if (!has_edge(edesc)) continue; + auto seg = view_graph()[edesc].segment; + if (segments_in_muons.find(seg) != segments_in_muons.end()) { + total_length += segment_track_length(seg); + } + } + + // Collect dQ and dx from ALL segments + std::vector vec_dQ; + std::vector vec_dx; + + for (auto edesc : this->edges()) { + if (!has_edge(edesc)) continue; + auto seg = view_graph()[edesc].segment; + + std::vector seg_dQ; + std::vector seg_dx; + + const auto& seg_fits = seg->fits(); + for (const auto& fit : seg_fits) { + vec_dQ.push_back(fit.dQ); + vec_dx.push_back(fit.dx); + } + + vec_dQ.insert(vec_dQ.end(), seg_dQ.begin(), seg_dQ.end()); + vec_dx.insert(vec_dx.end(), seg_dx.begin(), seg_dx.end()); + } + + // Calculate kinetic energies + data.kenergy_range = cal_kine_range(total_length, particle_type, particle_data); + data.kenergy_dQdx = cal_kine_dQdx(vec_dQ, vec_dx, recomb_model); + + // For long muon, use dQdx as best energy + data.kenergy_best = data.kenergy_dQdx; + + // Calculate initial direction from start segment + data.init_dir = segment_cal_dir_3vector(m_start_segment); + + // Set start point based on direction + auto& fits = m_start_segment->fits(); + int dirsign_val = m_start_segment->dirsign(); + if (dirsign_val == 1) { + data.start_point = fits.front().point; + } else { + data.start_point = fits.back().point; + } + + // Find farthest vertex that has at least one segment in segments_in_muons + double max_dis = 0; + VertexPtr farthest_vertex = nullptr; + + for (auto vdesc : this->nodes()) { + if (!has_node(vdesc)) continue; + auto vtx = view_graph()[vdesc].vertex; + + // Check if this vertex has at least one segment in segments_in_muons + bool flag_contain = false; + for (auto out_edge : boost::make_iterator_range(boost::out_edges(vdesc, m_full_graph))) { + if (!has_edge(out_edge)) continue; + auto seg = view_graph()[out_edge].segment; + if (segments_in_muons.find(seg) != segments_in_muons.end()) { + flag_contain = true; + break; + } + } + + if (flag_contain) { + double dis = (vtx->fit().point - data.start_point).magnitude(); + if (dis > max_dis) { + max_dis = dis; + farthest_vertex = vtx; + } + } + } + + // Set end point to the farthest vertex + if (farthest_vertex) { + data.end_point = farthest_vertex->fit().point; + } else { + // Fallback: use the other end of start segment + auto& fits = m_start_segment->fits(); + if (m_start_segment->dirsign() == 1) { + data.end_point = fits.back().point; + } else { + data.end_point = fits.front().point; + } + } + } } \ No newline at end of file From 5c9e6177e789eeb6315dfb39c6c1f71c0f9327a4 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sun, 4 Jan 2026 15:26:32 -0800 Subject: [PATCH 092/111] update code --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 7 +++ clus/inc/WireCellClus/PRShower.h | 2 + clus/src/NeutrinoShowerClustering.cxx | 51 +++++++++++++++++++++ 3 files changed, 60 insertions(+) create mode 100644 clus/src/NeutrinoShowerClustering.cxx diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index efdd5762..a7978014 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -1,6 +1,7 @@ #include "WireCellClus/PRGraph.h" #include "WireCellClus/Facade_Cluster.h" #include "WireCellClus/TrackFitting.h" +#include "WireCellClus/PRShower.h" namespace WireCell::Clus::PR { class PatternAlgorithms{ @@ -132,6 +133,12 @@ namespace WireCell::Clus::PR { // print information void print_segs_info(Graph& graph, Facade::Cluster& cluster, VertexPtr vertex= nullptr); + // shower related functions + void update_shower_maps(std::set& showers, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters); + + + + }; diff --git a/clus/inc/WireCellClus/PRShower.h b/clus/inc/WireCellClus/PRShower.h index e3496a21..8a274ac0 100644 --- a/clus/inc/WireCellClus/PRShower.h +++ b/clus/inc/WireCellClus/PRShower.h @@ -189,5 +189,7 @@ namespace WireCell::Clus::PR { }; + using ShowerPtr = std::shared_ptr; + } #endif diff --git a/clus/src/NeutrinoShowerClustering.cxx b/clus/src/NeutrinoShowerClustering.cxx new file mode 100644 index 00000000..10efac96 --- /dev/null +++ b/clus/src/NeutrinoShowerClustering.cxx @@ -0,0 +1,51 @@ +#include "WireCellClus/NeutrinoPatternBase.h" +#include "WireCellClus/PRSegmentFunctions.h" +#include "WireCellClus/PRShowerFunctions.h" +#include + +using namespace WireCell::Clus::PR; +using namespace WireCell::Clus; + +void PatternAlgorithms::update_shower_maps(std::set& showers, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters){ + // Clear all maps + map_vertex_to_shower.clear(); + map_vertex_in_shower.clear(); + map_segment_in_shower.clear(); + used_shower_clusters.clear(); + + // Iterate through all showers + for (auto shower : showers) { + // Map start vertex to shower + auto [start_vtx, conn_type] = shower->get_start_vertex_and_type(); + if (start_vtx) { + map_vertex_to_shower[start_vtx].insert(shower); + } + + // Fill maps using TrajectoryView - iterate through all vertices and segments in the shower + TrajectoryView& traj = shower->fill_maps(); + + // Fill map_vertex_in_shower with all vertices in this shower + for (auto vdesc : traj.nodes()) { + auto vtx = traj.view_graph()[vdesc].vertex; + if (vtx) { + map_vertex_in_shower[vtx] = shower; + } + } + + // Fill map_segment_in_shower with all segments in this shower + for (auto edesc : traj.edges()) { + auto seg = traj.view_graph()[edesc].segment; + if (seg) { + map_segment_in_shower[seg] = shower; + } + } + } + + // Collect all cluster IDs from segments in the map + for (auto it = map_segment_in_shower.begin(); it != map_segment_in_shower.end(); it++) { + auto seg = it->first; + if (seg && seg->cluster()) { + used_shower_clusters.insert(seg->cluster()); + } + } +} From 5cae6b9583873709e175eed1dcfdaf8f69560481 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Mon, 5 Jan 2026 18:37:19 -0800 Subject: [PATCH 093/111] catch up --- clus/inc/WireCellClus/DynamicPointCloud.h | 4 + clus/inc/WireCellClus/NeutrinoDLVertex.h | 0 clus/inc/WireCellClus/NeutrinoDeghoster.h | 0 clus/inc/WireCellClus/NeutrinoEnergyReco.h | 0 clus/inc/WireCellClus/NeutrinoKinematics.h | 0 clus/inc/WireCellClus/NeutrinoPatternBase.h | 4 +- .../inc/WireCellClus/NeutrinoTrackShowerSep.h | 0 clus/src/DynamicPointCloud.cxx | 77 +++++++++++++++++++ clus/src/NeutrinoDeghoster.cxx | 61 +++++++++++++++ clus/src/PRShower.cxx | 2 +- 10 files changed, 145 insertions(+), 3 deletions(-) delete mode 100644 clus/inc/WireCellClus/NeutrinoDLVertex.h delete mode 100644 clus/inc/WireCellClus/NeutrinoDeghoster.h delete mode 100644 clus/inc/WireCellClus/NeutrinoEnergyReco.h delete mode 100644 clus/inc/WireCellClus/NeutrinoKinematics.h delete mode 100644 clus/inc/WireCellClus/NeutrinoTrackShowerSep.h diff --git a/clus/inc/WireCellClus/DynamicPointCloud.h b/clus/inc/WireCellClus/DynamicPointCloud.h index 72d55e8d..ea41ee38 100644 --- a/clus/inc/WireCellClus/DynamicPointCloud.h +++ b/clus/inc/WireCellClus/DynamicPointCloud.h @@ -89,6 +89,10 @@ namespace WireCell::Clus::Facade { make_points_cluster(const Cluster *cluster, const std::map> &wpid_params, bool flag_wrap = false); + std::vector + make_points_cluster_steiner(const Cluster *cluster, + const std::map> &wpid_params, bool flag_wrap = false); + std::vector make_points_cluster_skeleton( const Cluster *cluster, const IDetectorVolumes::pointer dv, const std::map> &wpid_params, diff --git a/clus/inc/WireCellClus/NeutrinoDLVertex.h b/clus/inc/WireCellClus/NeutrinoDLVertex.h deleted file mode 100644 index e69de29b..00000000 diff --git a/clus/inc/WireCellClus/NeutrinoDeghoster.h b/clus/inc/WireCellClus/NeutrinoDeghoster.h deleted file mode 100644 index e69de29b..00000000 diff --git a/clus/inc/WireCellClus/NeutrinoEnergyReco.h b/clus/inc/WireCellClus/NeutrinoEnergyReco.h deleted file mode 100644 index e69de29b..00000000 diff --git a/clus/inc/WireCellClus/NeutrinoKinematics.h b/clus/inc/WireCellClus/NeutrinoKinematics.h deleted file mode 100644 index e69de29b..00000000 diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index a7978014..0610aa6e 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -136,8 +136,8 @@ namespace WireCell::Clus::PR { // shower related functions void update_shower_maps(std::set& showers, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters); - - + // deghost related functions ... + void order_clusters(Graph& graph, std::vector& ordered_clusters, std::map >& map_cluster_to_segments, std::map& map_cluster_total_length); diff --git a/clus/inc/WireCellClus/NeutrinoTrackShowerSep.h b/clus/inc/WireCellClus/NeutrinoTrackShowerSep.h deleted file mode 100644 index e69de29b..00000000 diff --git a/clus/src/DynamicPointCloud.cxx b/clus/src/DynamicPointCloud.cxx index 838b49a0..a594cbf9 100644 --- a/clus/src/DynamicPointCloud.cxx +++ b/clus/src/DynamicPointCloud.cxx @@ -452,6 +452,83 @@ std::vector Clus::Facade::make_points_cluster( return dpc_points; } +std::vector Clus::Facade::make_points_cluster_steiner(const Cluster *cluster, const std::map> &wpid_params, bool flag_wrap){ + if (!cluster) { + SPDLOG_WARN("make_points_cluster_steiner: null cluster return empty points"); + return {}; + } + + // Check if steiner point cloud exists + if (!cluster->has_pc("steiner_pc")) { + SPDLOG_WARN("make_points_cluster_steiner: cluster has no steiner_pc"); + return {}; + } + + const auto& steiner_pc = cluster->get_pc("steiner_pc"); + const auto& coords = cluster->get_default_scope().coords; + const auto& x_coords = steiner_pc.get(coords.at(0))->elements(); + const auto& y_coords = steiner_pc.get(coords.at(1))->elements(); + const auto& z_coords = steiner_pc.get(coords.at(2))->elements(); + const auto& wpid_array = steiner_pc.get("wpid")->elements(); + + const size_t num_points = x_coords.size(); + std::vector dpc_points; + dpc_points.reserve(num_points); + + // Cache commonly referenced WPIDs and their params to avoid map lookups + std::unordered_map> cached_params; + + for (size_t ipt = 0; ipt < num_points; ++ipt) { + geo_point_t pt(x_coords[ipt], y_coords[ipt], z_coords[ipt]); + const auto wpid = wpid_array[ipt]; + int wpid_ident = wpid.ident(); + + // Check cache first, then populate if needed + auto param_it = cached_params.find(wpid_ident); + if (param_it == cached_params.end()) { + auto wpid_it = wpid_params.find(wpid); + if (wpid_it == wpid_params.end()) { + raise("make_points_cluster_steiner: missing wpid params for wpid %s", wpid.name()); + } + param_it = cached_params.emplace(wpid_ident, wpid_it->second).first; + } + + const auto &[drift_dir, angle_u, angle_v, angle_w] = param_it->second; + const double angle_uvw[3] = {angle_u, angle_v, angle_w}; + + DynamicPointCloud::DPCPoint point; + point.x = pt.x(); + point.y = pt.y(); + point.z = pt.z(); + point.wpid = wpid.ident(); + point.cluster = cluster; + point.blob = nullptr; // Steiner points don't have blob associations + + // Pre-allocate vectors with correct size + point.x_2d.resize(3); + point.y_2d.resize(3); + point.wpid_2d.resize(3); + + if (flag_wrap){ + fill_wrap_points(cluster, pt, wpid, point.x_2d, point.y_2d, point.wpid_2d); + }else{ + for (size_t pindex = 0; pindex < 3; ++pindex) { + point.x_2d[pindex].push_back(point.x); + point.y_2d[pindex].push_back(cos(angle_uvw[pindex]) * point.z - sin(angle_uvw[pindex]) * point.y); + point.wpid_2d[pindex].push_back(wpid.ident()); + } + } + + // Steiner points don't have wire indices, use bogus values + point.wind = wind_bogus; + point.dist_cut = dist_cut_bogus; + + dpc_points.push_back(std::move(point)); + } + + return dpc_points; +} + std::vector Clus::Facade::make_points_direct(const Cluster *cluster, const IDetectorVolumes::pointer dv, const std::map> &wpid_params, std::vector>& points_info, bool flag_wrap){ std::vector dpc_points; diff --git a/clus/src/NeutrinoDeghoster.cxx b/clus/src/NeutrinoDeghoster.cxx index e69de29b..a8d743ca 100644 --- a/clus/src/NeutrinoDeghoster.cxx +++ b/clus/src/NeutrinoDeghoster.cxx @@ -0,0 +1,61 @@ +#include "WireCellClus/NeutrinoPatternBase.h" +#include "WireCellClus/PRSegmentFunctions.h" + +using namespace WireCell::Clus::PR; +using namespace WireCell::Clus; + +namespace { + // Helper function to sort clusters by total length in descending order + bool sortbysec(const std::pair& a, + const std::pair& b) { + return (a.second > b.second); + } +} + +void PatternAlgorithms::order_clusters(Graph& graph, std::vector& ordered_clusters, std::map >& map_cluster_to_segments, std::map& map_cluster_total_length){ + // Clear output containers + map_cluster_to_segments.clear(); + map_cluster_total_length.clear(); + ordered_clusters.clear(); + + // Iterate through all segments in the graph + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr seg = graph[*eit].segment; + + if (!seg || !seg->cluster()) continue; + + // Get the segment's cluster + Facade::Cluster* cluster = seg->cluster(); + + // Calculate segment length + double length = segment_track_length(seg); + + // Check if this is the first segment for this cluster + if (map_cluster_total_length.find(cluster) == map_cluster_total_length.end()) { + // First segment for this cluster - initialize + std::vector segments; + segments.push_back(seg); + map_cluster_to_segments[cluster] = segments; + map_cluster_total_length[cluster] = length; + } else { + // Add to existing cluster + map_cluster_to_segments[cluster].push_back(seg); + map_cluster_total_length[cluster] += length; + } + } + + // Create a vector of pairs (cluster, total_length) for sorting + std::vector> temp_pair_vec; + for (auto it = map_cluster_total_length.begin(); it != map_cluster_total_length.end(); ++it) { + temp_pair_vec.push_back(std::make_pair(it->first, it->second)); + } + + // Sort clusters by total length in descending order + std::sort(temp_pair_vec.begin(), temp_pair_vec.end(), sortbysec); + + // Fill ordered_clusters with sorted results + for (auto it = temp_pair_vec.begin(); it != temp_pair_vec.end(); ++it) { + ordered_clusters.push_back(it->first); + } +} diff --git a/clus/src/PRShower.cxx b/clus/src/PRShower.cxx index 483d13f4..71003d24 100644 --- a/clus/src/PRShower.cxx +++ b/clus/src/PRShower.cxx @@ -1038,7 +1038,7 @@ namespace WireCell::Clus::PR { void Shower::calculate_kinematics_long_muon(std::set& segments_in_muons, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model){ // Get particle type from start segment int particle_type = abs(m_start_segment->particle_info()->pdg()); - double particle_mass = m_start_segment->particle_info()->mass(); + // double particle_mass = m_start_segment->particle_info()->mass(); unset_flags(ShowerFlags::kKinematics); From 675ce5466033927e4afb1c1cfa9bbd1071dae9e0 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Mon, 5 Jan 2026 18:56:33 -0800 Subject: [PATCH 094/111] implement the deghost_clusters fucntion --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 1 + clus/src/NeutrinoDeghoster.cxx | 254 ++++++++++++++++++++ 2 files changed, 255 insertions(+) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 0610aa6e..186d64b0 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -138,6 +138,7 @@ namespace WireCell::Clus::PR { // deghost related functions ... void order_clusters(Graph& graph, std::vector& ordered_clusters, std::map >& map_cluster_to_segments, std::map& map_cluster_total_length); + void deghost_clusters(Graph& graph, std::vector& all_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); diff --git a/clus/src/NeutrinoDeghoster.cxx b/clus/src/NeutrinoDeghoster.cxx index a8d743ca..d68d46db 100644 --- a/clus/src/NeutrinoDeghoster.cxx +++ b/clus/src/NeutrinoDeghoster.cxx @@ -59,3 +59,257 @@ void PatternAlgorithms::order_clusters(Graph& graph, std::vectorfirst); } } + +void PatternAlgorithms::deghost_clusters(Graph& graph, std::vector& all_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv){ + // Order clusters by total segment length + std::map> map_cluster_to_segments; + std::map map_cluster_total_length; + std::vector ordered_clusters; + order_clusters(graph, ordered_clusters, map_cluster_to_segments, map_cluster_total_length); + + if (ordered_clusters.empty()) return; + + // Get first cluster's grouping to access wpids and dead channel info + auto* first_grouping = ordered_clusters[0]->grouping(); + if (!first_grouping) return; + + // Build wpid_params + const auto& wpids = first_grouping->wpids(); + std::map> wpid_params; + std::map> wpid_U_dir, wpid_V_dir, wpid_W_dir; + std::set apas; + Facade::compute_wireplane_params(wpids, dv, wpid_params, wpid_U_dir, wpid_V_dir, wpid_W_dir, apas); + + // Get dead channel maps + std::map>>> af_dead_u_index; + std::map>>> af_dead_v_index; + std::map>>> af_dead_w_index; + + for (const auto& wpid : wpids) { + int apa = wpid.apa(); + int face = wpid.face(); + af_dead_u_index[apa][face] = first_grouping->get_dead_winds(apa, face, 0); + af_dead_v_index[apa][face] = first_grouping->get_dead_winds(apa, face, 1); + af_dead_w_index[apa][face] = first_grouping->get_dead_winds(apa, face, 2); + } + + // Create global point clouds + auto global_point_cloud = std::make_shared(wpid_params); + auto global_steiner_point_cloud = std::make_shared(wpid_params); + auto global_skeleton_cloud = std::make_shared(wpid_params); + + // Add points from clusters not in ordered list + for (auto cluster : all_clusters) { + if (map_cluster_total_length.find(cluster) == map_cluster_total_length.end()) { + global_point_cloud->add_points(Facade::make_points_cluster(cluster, wpid_params, true)); + } + } + + std::vector to_be_removed_clusters; + + // Process ordered clusters + for (size_t i = 0; i < ordered_clusters.size(); i++) { + if (i == 0) { + // First cluster: add all its points + global_point_cloud->add_points(Facade::make_points_cluster(ordered_clusters[i], wpid_params, true)); + global_steiner_point_cloud->add_points(Facade::make_points_cluster_steiner(ordered_clusters[i], wpid_params, true)); + + // Add skeleton points from segments + auto it = map_cluster_to_segments.find(ordered_clusters[i]); + if (it != map_cluster_to_segments.end()) { + for (auto seg : it->second) { + // Create point-plane pairs from segment fits + std::vector> point_plane_pairs; + for (const auto& fit : seg->fits()) { + WirePlaneId wpid = dv->contained_by(fit.point); + point_plane_pairs.emplace_back(fit.point, wpid); + } + global_skeleton_cloud->add_points(Facade::make_points_direct(ordered_clusters[i], dv, wpid_params, point_plane_pairs, true)); + } + } + } else { + // Process subsequent clusters + Facade::Cluster* cluster = ordered_clusters[i]; + int num_dead[3] = {0, 0, 0}; + int num_unique[3] = {0, 0, 0}; + int num_total_points = 0; + + double dis_cut = 1.2 * units::cm; + + auto it = map_cluster_to_segments.find(cluster); + if (it != map_cluster_to_segments.end()) { + for (auto seg : it->second) { + for (const auto& fit : seg->fits()) { + Facade::geo_point_t test_point = fit.point; + num_total_points++; + + WirePlaneId test_wpid = dv->contained_by(test_point); + int apa = test_wpid.apa(); + int face = test_wpid.face(); + + // Get point in raw coordinates for dead channel check + auto transform = track_fitter.get_pc_transforms()->pc_transform(cluster->get_scope_transform(cluster->get_default_scope())); + double cluster_t0 = cluster->get_cluster_t0(); + Facade::geo_point_t p_raw = transform->backward(test_point, cluster_t0, face, apa); + + // const auto& winds = cluster->wire_indices(); + + // U plane + bool flag_dead = false; + if (af_dead_u_index.find(apa) != af_dead_u_index.end() && + af_dead_u_index[apa].find(face) != af_dead_u_index[apa].end()) { + flag_dead = cluster->grouping()->get_closest_dead_chs(p_raw, 1, apa, face, 0); + } + + if (!flag_dead) { + auto results = global_point_cloud->get_closest_2d_point_info(test_point, 0, face, apa); + if (std::get<0>(results) <= dis_cut / 2.) { + // Overlap with global point cloud + } else { + results = global_steiner_point_cloud->get_closest_2d_point_info(test_point, 0, face, apa); + if (std::get<0>(results) <= dis_cut * 2. / 3.) { + // Overlap with steiner cloud + } else { + results = global_skeleton_cloud->get_closest_2d_point_info(test_point, 0, face, apa); + if (std::get<0>(results) <= dis_cut * 6. / 4.) { + // Overlap with skeleton cloud + } else { + num_unique[0]++; + } + } + } + } else { + num_dead[0]++; + } + + // V plane + flag_dead = false; + if (af_dead_v_index.find(apa) != af_dead_v_index.end() && + af_dead_v_index[apa].find(face) != af_dead_v_index[apa].end()) { + flag_dead = cluster->grouping()->get_closest_dead_chs(p_raw, 1, apa, face, 1); + } + + if (!flag_dead) { + auto results = global_point_cloud->get_closest_2d_point_info(test_point, 1, face, apa); + if (std::get<0>(results) <= dis_cut / 2.) { + } else { + results = global_steiner_point_cloud->get_closest_2d_point_info(test_point, 1, face, apa); + if (std::get<0>(results) <= dis_cut * 2. / 3.) { + } else { + results = global_skeleton_cloud->get_closest_2d_point_info(test_point, 1, face, apa); + if (std::get<0>(results) <= dis_cut * 6. / 4.) { + } else { + num_unique[1]++; + } + } + } + } else { + num_dead[1]++; + } + + // W plane + flag_dead = false; + if (af_dead_w_index.find(apa) != af_dead_w_index.end() && + af_dead_w_index[apa].find(face) != af_dead_w_index[apa].end()) { + flag_dead = cluster->grouping()->get_closest_dead_chs(p_raw, 1, apa, face, 2); + } + + if (!flag_dead) { + auto results = global_point_cloud->get_closest_2d_point_info(test_point, 2, face, apa); + if (std::get<0>(results) <= dis_cut / 2.) { + } else { + results = global_steiner_point_cloud->get_closest_2d_point_info(test_point, 2, face, apa); + if (std::get<0>(results) <= dis_cut * 2. / 3.) { + } else { + results = global_skeleton_cloud->get_closest_2d_point_info(test_point, 2, face, apa); + if (std::get<0>(results) <= dis_cut * 6. / 4.) { + } else { + num_unique[2]++; + } + } + } + } else { + num_dead[2]++; + } + } + } + } + + // Calculate percentages + bool flag_add = true; + if (num_total_points > 0) { + double unique_percent_u = num_unique[0] * 1.0 / num_total_points; + double unique_percent_v = num_unique[1] * 1.0 / num_total_points; + double unique_percent_w = num_unique[2] * 1.0 / num_total_points; + + double dead_percent_u = num_dead[0] * 1.0 / num_total_points; + double dead_percent_v = num_dead[1] * 1.0 / num_total_points; + double dead_percent_w = num_dead[2] * 1.0 / num_total_points; + + double max_unique_percent = std::max({unique_percent_u, unique_percent_v, unique_percent_w}); + double min_unique_percent = std::min({unique_percent_u, unique_percent_v, unique_percent_w}); + double ave_unique_percent = (unique_percent_u + unique_percent_v + unique_percent_w) / 3.; + double max_dead_percent = std::max({dead_percent_u, dead_percent_v, dead_percent_w}); + + // Apply ghosting criteria + if ((max_dead_percent >= 0.8 && max_unique_percent <= 0.35 && ave_unique_percent <= 0.16 && min_unique_percent <= 0.08) || + (max_unique_percent <= 0.1 && ave_unique_percent <= 0.05 && min_unique_percent <= 0.025) || + (max_dead_percent < 0.8 && max_dead_percent >= 0.7 && max_unique_percent <= 0.2 && ave_unique_percent <= 0.1 && min_unique_percent <= 0.05)) { + flag_add = false; + } + + // Additional check for one dead plane + if ((num_dead[0] == num_total_points || num_dead[1] == num_total_points || num_dead[2] == num_total_points) && + ((num_unique[0] == 0 && num_unique[1] == 0) || (num_unique[0] == 0 && num_unique[2] == 0) || (num_unique[2] == 0 && num_unique[1] == 0)) && + flag_add && max_unique_percent < 0.75) { + flag_add = false; + } + } + + if (flag_add) { + // Add to global clouds + global_point_cloud->add_points(Facade::make_points_cluster(cluster, wpid_params, true)); + global_steiner_point_cloud->add_points(Facade::make_points_cluster_steiner(cluster, wpid_params, true)); + + auto it = map_cluster_to_segments.find(cluster); + if (it != map_cluster_to_segments.end()) { + for (auto seg : it->second) { + std::vector> point_plane_pairs; + for (const auto& fit : seg->fits()) { + WirePlaneId wpid = dv->contained_by(fit.point); + point_plane_pairs.emplace_back(fit.point, wpid); + } + global_skeleton_cloud->add_points(Facade::make_points_direct(cluster, dv, wpid_params, point_plane_pairs, true)); + } + } + } else { + to_be_removed_clusters.push_back(cluster); + } + } + } + + // Remove segments from ghosted clusters + for (auto cluster : to_be_removed_clusters) { + auto it = map_cluster_to_segments.find(cluster); + if (it != map_cluster_to_segments.end()) { + for (auto seg : it->second) { + remove_segment(graph, seg); + } + } + } + + // Clean up orphaned vertices + std::vector tmp_vertices; + auto [vbegin, vend] = boost::vertices(graph); + for (auto vit = vbegin; vit != vend; ++vit) { + VertexPtr vtx = graph[*vit].vertex; + if (vtx && boost::out_degree(*vit, graph) == 0) { + tmp_vertices.push_back(vtx); + } + } + + for (auto vtx : tmp_vertices) { + remove_vertex(graph, vtx); + } +} + From e233d864422ff25045e7be90792cf0e609c6540d Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Tue, 6 Jan 2026 05:04:41 -0800 Subject: [PATCH 095/111] implement deghost_segments function --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 2 + clus/src/NeutrinoDeghoster.cxx | 251 ++++++++++++++++++++ 2 files changed, 253 insertions(+) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 186d64b0..5b40134a 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -139,6 +139,8 @@ namespace WireCell::Clus::PR { // deghost related functions ... void order_clusters(Graph& graph, std::vector& ordered_clusters, std::map >& map_cluster_to_segments, std::map& map_cluster_total_length); void deghost_clusters(Graph& graph, std::vector& all_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); + void order_segments(std::vector& ordered_segments, std::vector& segments); + void deghost_segments(Graph& graph, std::map map_cluster_main_vertices, std::vector& all_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); diff --git a/clus/src/NeutrinoDeghoster.cxx b/clus/src/NeutrinoDeghoster.cxx index d68d46db..2054e8b4 100644 --- a/clus/src/NeutrinoDeghoster.cxx +++ b/clus/src/NeutrinoDeghoster.cxx @@ -10,6 +10,12 @@ namespace { const std::pair& b) { return (a.second > b.second); } + + // Helper function to sort segments by length in descending order + bool sortbysec1(const std::pair& a, + const std::pair& b) { + return (a.second > b.second); + } } void PatternAlgorithms::order_clusters(Graph& graph, std::vector& ordered_clusters, std::map >& map_cluster_to_segments, std::map& map_cluster_total_length){ @@ -313,3 +319,248 @@ void PatternAlgorithms::deghost_clusters(Graph& graph, std::vector& ordered_segments, std::vector& segments){ + // Clear output container + ordered_segments.clear(); + + // Create vector of pairs (segment, length) + std::vector> temp_pair_vec; + for (auto seg : segments) { + double length = segment_track_length(seg); + temp_pair_vec.push_back(std::make_pair(seg, length)); + } + + // Sort by length in descending order + std::sort(temp_pair_vec.begin(), temp_pair_vec.end(), sortbysec1); + + // Fill ordered_segments with sorted results + for (auto it = temp_pair_vec.begin(); it != temp_pair_vec.end(); ++it) { + ordered_segments.push_back(it->first); + } +} + +void PatternAlgorithms::deghost_segments(Graph& graph, std::map map_cluster_main_vertices, std::vector& all_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv) { + // Order clusters by total segment length + std::map> map_cluster_to_segments; + std::map map_cluster_total_length; + std::vector ordered_clusters; + order_clusters(graph, ordered_clusters, map_cluster_to_segments, map_cluster_total_length); + + if (ordered_clusters.empty()) return; + + // Get first cluster's grouping to access wpids + auto* first_grouping = ordered_clusters[0]->grouping(); + if (!first_grouping) return; + + // Build wpid_params + const auto& wpids = first_grouping->wpids(); + std::map> wpid_params; + std::map> wpid_U_dir, wpid_V_dir, wpid_W_dir; + std::set apas; + Facade::compute_wireplane_params(wpids, dv, wpid_params, wpid_U_dir, wpid_V_dir, wpid_W_dir, apas); + + // Create global point clouds + auto global_point_cloud = std::make_shared(wpid_params); + auto global_steiner_point_cloud = std::make_shared(wpid_params); + auto global_skeleton_cloud = std::make_shared(wpid_params); + + // Add points from clusters not in ordered list + for (auto cluster : all_clusters) { + if (map_cluster_total_length.find(cluster) == map_cluster_total_length.end()) { + global_point_cloud->add_points(Facade::make_points_cluster(cluster, wpid_params, true)); + } + } + + if (global_point_cloud->get_points().size() == 0) return; + + double dis_cut = 1.2 * units::cm; + + // Process ordered clusters + for (size_t i = 0; i < ordered_clusters.size(); i++) { + Facade::Cluster* cluster = ordered_clusters[i]; + + // Order segments within this cluster + auto it_cluster = map_cluster_to_segments.find(cluster); + if (it_cluster == map_cluster_to_segments.end()) continue; + + std::vector ordered_segments; + order_segments(ordered_segments, it_cluster->second); + + // Process each segment + for (auto seg : ordered_segments) { + bool flag_add_seg = true; + + // Get segment properties + double medium_dQ_dx = segment_median_dQ_dx(seg); + double length = segment_track_length(seg); + + // Get vertices + auto edesc = seg->get_descriptor(); + auto source_vdesc = boost::source(edesc, graph); + auto target_vdesc = boost::target(edesc, graph); + VertexPtr v1 = graph[source_vdesc].vertex; + VertexPtr v2 = graph[target_vdesc].vertex; + + // Count connections at each vertex + int start_n = boost::out_degree(source_vdesc, graph); + int end_n = boost::out_degree(target_vdesc, graph); + + // Check if this is a terminal segment with low dQ/dx + if ((start_n == 1 || end_n == 1) && medium_dQ_dx < 1.1 * 43e3 / units::cm && length > 3.6 * units::cm) { + int num_dead[3] = {0, 0, 0}; + int num_unique[3] = {0, 0, 0}; + int num_total_points = 0; + + // Check each fit point + for (const auto& fit : seg->fits()) { + Facade::geo_point_t test_point = fit.point; + num_total_points++; + + WirePlaneId test_wpid = dv->contained_by(test_point); + int apa = test_wpid.apa(); + int face = test_wpid.face(); + + // Get point in raw coordinates + auto transform = track_fitter.get_pc_transforms()->pc_transform(cluster->get_scope_transform(cluster->get_default_scope())); + double cluster_t0 = cluster->get_cluster_t0(); + Facade::geo_point_t p_raw = transform->backward(test_point, cluster_t0, face, apa); + + // Check U plane + bool flag_dead = cluster->grouping()->get_closest_dead_chs(p_raw, 1, apa, face, 0); + if (!flag_dead) { + bool flag_in = false; + + auto results = global_point_cloud->get_closest_2d_point_info(test_point, 0, face, apa); + if (std::get<0>(results) <= dis_cut * 2. / 3.) flag_in = true; + + if (global_steiner_point_cloud->get_points().size() != 0) { + results = global_steiner_point_cloud->get_closest_2d_point_info(test_point, 0, face, apa); + if (std::get<0>(results) <= dis_cut * 2. / 3.) flag_in = true; + } + + if (global_skeleton_cloud->get_points().size() != 0) { + results = global_skeleton_cloud->get_closest_2d_point_info(test_point, 0, face, apa); + if (std::get<0>(results) <= dis_cut * 3. / 4.) flag_in = true; + } + + if (!flag_in) num_unique[0]++; + } else { + num_dead[0]++; + } + + // Check V plane + flag_dead = cluster->grouping()->get_closest_dead_chs(p_raw, 1, apa, face, 1); + if (!flag_dead) { + bool flag_in = false; + + auto results = global_point_cloud->get_closest_2d_point_info(test_point, 1, face, apa); + if (std::get<0>(results) <= dis_cut * 2. / 3.) flag_in = true; + + if (global_steiner_point_cloud->get_points().size() != 0) { + results = global_steiner_point_cloud->get_closest_2d_point_info(test_point, 1, face, apa); + if (std::get<0>(results) <= dis_cut * 2. / 3.) flag_in = true; + } + + if (global_skeleton_cloud->get_points().size() != 0) { + results = global_skeleton_cloud->get_closest_2d_point_info(test_point, 1, face, apa); + if (std::get<0>(results) <= dis_cut * 3. / 4.) flag_in = true; + } + + if (!flag_in) num_unique[1]++; + } else { + num_dead[1]++; + } + + // Check W plane + flag_dead = cluster->grouping()->get_closest_dead_chs(p_raw, 1, apa, face, 2); + if (!flag_dead) { + bool flag_in = false; + + auto results = global_point_cloud->get_closest_2d_point_info(test_point, 2, face, apa); + if (std::get<0>(results) <= dis_cut * 2. / 3.) flag_in = true; + + if (global_steiner_point_cloud->get_points().size() != 0) { + results = global_steiner_point_cloud->get_closest_2d_point_info(test_point, 2, face, apa); + if (std::get<0>(results) <= dis_cut * 2. / 3.) flag_in = true; + } + + if (global_skeleton_cloud->get_points().size() != 0) { + results = global_skeleton_cloud->get_closest_2d_point_info(test_point, 2, face, apa); + if (std::get<0>(results) <= dis_cut * 3. / 4.) flag_in = true; + } + + if (!flag_in) num_unique[2]++; + } else { + num_dead[2]++; + } + } + + // If all points overlap with existing clouds, mark for removal + if (num_unique[0] + num_unique[1] + num_unique[2] == 0) { + flag_add_seg = false; + } + + (void) num_total_points; // num_total_points is not used further + } + + if (flag_add_seg) { + // Add segment fits to skeleton cloud + std::vector> point_plane_pairs; + for (const auto& fit : seg->fits()) { + WirePlaneId wpid = dv->contained_by(fit.point); + point_plane_pairs.emplace_back(fit.point, wpid); + } + global_skeleton_cloud->add_points(Facade::make_points_direct(cluster, dv, wpid_params, point_plane_pairs, true)); + } else { + // Protect main vertex - don't remove segment if it's the only one connected to the main vertex + if (map_cluster_main_vertices.find(cluster) != map_cluster_main_vertices.end()) { + VertexPtr main_vtx = map_cluster_main_vertices[cluster]; + + // Check if this segment is connected to the main vertex + auto edesc = seg->get_descriptor(); + auto source_vdesc = boost::source(edesc, graph); + auto target_vdesc = boost::target(edesc, graph); + VertexPtr v1 = graph[source_vdesc].vertex; + VertexPtr v2 = graph[target_vdesc].vertex; + + // If segment connects to main vertex and it's the only segment at that vertex, keep it + if ((v1 == main_vtx && boost::out_degree(source_vdesc, graph) == 1) || + (v2 == main_vtx && boost::out_degree(target_vdesc, graph) == 1)) { + flag_add_seg = true; + } + } + + if (flag_add_seg) { + // Keep the segment to protect main vertex + std::vector> point_plane_pairs; + for (const auto& fit : seg->fits()) { + WirePlaneId wpid = dv->contained_by(fit.point); + point_plane_pairs.emplace_back(fit.point, wpid); + } + global_skeleton_cloud->add_points(Facade::make_points_direct(cluster, dv, wpid_params, point_plane_pairs, true)); + } else { + // Remove segment + remove_segment(graph, seg); + } + } + } + + // Add cluster points to global clouds after processing its segments + global_point_cloud->add_points(Facade::make_points_cluster(cluster, wpid_params, true)); + global_steiner_point_cloud->add_points(Facade::make_points_cluster_steiner(cluster, wpid_params, true)); + } + + // Clean up orphaned vertices + std::vector tmp_vertices; + auto [vbegin, vend] = boost::vertices(graph); + for (auto vit = vbegin; vit != vend; ++vit) { + VertexPtr vtx = graph[*vit].vertex; + if (vtx && boost::out_degree(*vit, graph) == 0) { + tmp_vertices.push_back(vtx); + } + } + + for (auto vtx : tmp_vertices) { + remove_vertex(graph, vtx); + } +} From 95009cf7dcd903d1c89953d1e433118d6ea7e6c7 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Tue, 6 Jan 2026 05:09:40 -0800 Subject: [PATCH 096/111] update code --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 2 +- clus/src/NeutrinoDeghoster.cxx | 36 +++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 5b40134a..70ba5274 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -141,7 +141,7 @@ namespace WireCell::Clus::PR { void deghost_clusters(Graph& graph, std::vector& all_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); void order_segments(std::vector& ordered_segments, std::vector& segments); void deghost_segments(Graph& graph, std::map map_cluster_main_vertices, std::vector& all_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); - + void deghosting(Graph& graph, std::map map_cluster_main_vertices, std::vector& all_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv ); }; diff --git a/clus/src/NeutrinoDeghoster.cxx b/clus/src/NeutrinoDeghoster.cxx index 2054e8b4..e16ca2ae 100644 --- a/clus/src/NeutrinoDeghoster.cxx +++ b/clus/src/NeutrinoDeghoster.cxx @@ -564,3 +564,39 @@ void PatternAlgorithms::deghost_segments(Graph& graph, std::map map_cluster_main_vertices, std::vector& all_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv ){ + // Call deghost_clusters + deghost_clusters(graph, all_clusters, track_fitter, dv); + + // Call deghost_segments + deghost_segments(graph, map_cluster_main_vertices, all_clusters, track_fitter, dv); + + // Clean up map_cluster_main_vertices by removing clusters whose main vertices no longer have any segments + std::set temp_clusters; + for (auto it = map_cluster_main_vertices.begin(); it != map_cluster_main_vertices.end(); it++) { + Facade::Cluster* cluster = it->first; + VertexPtr vertex = it->second; + + // Check if this vertex still has segments connected to it by checking the graph + auto [vbegin, vend] = boost::vertices(graph); + bool vertex_has_connections = false; + for (auto vit = vbegin; vit != vend; ++vit) { + if (graph[*vit].vertex == vertex) { + if (boost::out_degree(*vit, graph) > 0) { + vertex_has_connections = true; + } + break; + } + } + if (!vertex_has_connections) { + temp_clusters.insert(cluster); + } + } + + // Remove the clusters that no longer have valid main vertices + for (auto it = temp_clusters.begin(); it != temp_clusters.end(); it++) { + map_cluster_main_vertices.erase(*it); + } +} From 49442c6e5f9e723fac24dd9bc39c0690c43e723c Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Tue, 6 Jan 2026 09:58:17 -0800 Subject: [PATCH 097/111] implement two functions --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 3 + clus/src/NeutrinoShowerClustering.cxx | 491 ++++++++++++++++++++ 2 files changed, 494 insertions(+) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 70ba5274..c910b3ec 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -135,6 +135,9 @@ namespace WireCell::Clus::PR { // shower related functions void update_shower_maps(std::set& showers, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters); + void shower_clustering_with_nv_in_main_cluster(Graph& graph, VertexPtr main_vertex, std::set& showers, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, std::set& vertices_in_long_muon, std::set& segments_in_long_muon); + void shower_clustering_connecting_to_main_vertex(Graph& graph, VertexPtr main_vertex, std::set& showers, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters); + // deghost related functions ... void order_clusters(Graph& graph, std::vector& ordered_clusters, std::map >& map_cluster_to_segments, std::map& map_cluster_total_length); diff --git a/clus/src/NeutrinoShowerClustering.cxx b/clus/src/NeutrinoShowerClustering.cxx index 10efac96..cfff5c62 100644 --- a/clus/src/NeutrinoShowerClustering.cxx +++ b/clus/src/NeutrinoShowerClustering.cxx @@ -49,3 +49,494 @@ void PatternAlgorithms::update_shower_maps(std::set& showers, std::m } } } + +void PatternAlgorithms::shower_clustering_with_nv_in_main_cluster(Graph& graph, VertexPtr main_vertex, std::set& showers, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, std::set& vertices_in_long_muon, std::set& segments_in_long_muon){ + if (!main_vertex) return; + + // Build map_vertex_segments from graph + std::map> map_vertex_segments; + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr seg = graph[*eit].segment; + if (!seg) continue; + + auto source_vdesc = boost::source(*eit, graph); + auto target_vdesc = boost::target(*eit, graph); + VertexPtr v1 = graph[source_vdesc].vertex; + VertexPtr v2 = graph[target_vdesc].vertex; + + if (v1) map_vertex_segments[v1].insert(seg); + if (v2) map_vertex_segments[v2].insert(seg); + } + + // Step 1: Collect segments from showers starting at main_vertex + std::set used_segments; + for (auto shower : showers) { + auto [start_vtx, conn_type] = shower->get_start_vertex_and_type(); + if (start_vtx == main_vertex) { + // Collect all segments from this shower + for (auto edesc : shower->edges()) { + auto seg = shower->view_graph()[edesc].segment; + if (seg) { + used_segments.insert(seg); + } + } + + // If there's only 1 segment and it's short, clear used_segments + if (used_segments.size() == 1 && segment_track_length(*used_segments.begin()) < 8 * units::cm) { + used_segments.clear(); + } + } + } + + // Step 2: If used_segments is empty, try to create new showers + if (used_segments.empty()) { + std::set del_showers; + std::map map_shower_max_sg; + ShowerPtr max_shower = nullptr; + double max_length = 0; + + // Get segments connected to main_vertex + auto& main_vtx_segments = map_vertex_segments[main_vertex]; + + for (auto sg : main_vtx_segments) { + // Skip if segment is in a shower + if (map_segment_in_shower.find(sg) != map_segment_in_shower.end()) continue; + + // Skip segments in long muon + if (segments_in_long_muon.find(sg) != segments_in_long_muon.end()) continue; + + // Get segment properties + double medium_dQ_dx = segment_median_dQ_dx(sg); + double medium_dQ_dx_1 = medium_dQ_dx / (43e3 / units::cm); + + // Get particle type if available + int particle_type = 0; + if (sg->has_particle_info() && sg->particle_info()) { + particle_type = sg->particle_info()->pdg(); + } + + // Skip segments with certain particle types and high dQ/dx + if ((particle_type == 11) || + (particle_type == 2212 && (medium_dQ_dx_1 > 1.45 || medium_dQ_dx_1 > 2.7)) || + (particle_type == 211 && medium_dQ_dx_1 > 2.0)) { + continue; + } + + // Create a new shower + ShowerPtr shower = std::make_shared(graph); + shower->set_start_vertex(main_vertex, 1); + shower->set_start_segment(sg); + shower->complete_structure_with_start_segment(used_segments); + + // Analyze shower structure + int n_tracks = 0; + int n_showers = 0; + double total_length = 0; + double max_seg_length = 0; + SegmentPtr max_sg = nullptr; + bool flag_good_track = false; + + // Iterate through shower segments + for (auto edesc : shower->edges()) { + auto sg1 = shower->view_graph()[edesc].segment; + if (!sg1) continue; + + double length = segment_track_length(sg1); + double medium_dQ_dx_sg = segment_median_dQ_dx(sg1); + double medium_dQ_dx_norm = medium_dQ_dx_sg / (43e3 / units::cm); + + // Check if segment is a shower + bool is_shower = sg1->flags_any(SegmentFlags::kShowerTrajectory) || + sg1->flags_any(SegmentFlags::kShowerTopology); + if (is_shower) n_showers++; + + n_tracks++; + total_length += length; + + if (max_seg_length < length) { + max_seg_length = length; + max_sg = sg1; + } + + // Check for good track properties + if (!sg1->dir_weak() && (length > 3.6 * units::cm || (length > 2.4 * units::cm && medium_dQ_dx_norm > 2.5))) { + // Find end vertex of this segment + auto seg_edesc = sg1->get_descriptor(); + auto source_vdesc = boost::source(seg_edesc, graph); + auto target_vdesc = boost::target(seg_edesc, graph); + VertexPtr v1 = graph[source_vdesc].vertex; + VertexPtr v2 = graph[target_vdesc].vertex; + + // Determine end vertex based on dirsign + VertexPtr end_vertex = nullptr; + if (sg1->dirsign() == 1) { + // Check which vertex is at the end + if (!sg1->fits().empty() && v1 && v2) { + auto& fits = sg1->fits(); + double dist1 = (fits.back().point - (v1->fit().valid() ? v1->fit().point : v1->wcpt().point)).magnitude(); + double dist2 = (fits.back().point - (v2->fit().valid() ? v2->fit().point : v2->wcpt().point)).magnitude(); + end_vertex = (dist1 < dist2) ? v1 : v2; + } + } else if (sg1->dirsign() == -1) { + if (!sg1->fits().empty() && v1 && v2) { + auto& fits = sg1->fits(); + double dist1 = (fits.front().point - (v1->fit().valid() ? v1->fit().point : v1->wcpt().point)).magnitude(); + double dist2 = (fits.front().point - (v2->fit().valid() ? v2->fit().point : v2->wcpt().point)).magnitude(); + end_vertex = (dist1 < dist2) ? v1 : v2; + } + } + + if (end_vertex && map_vertex_segments.find(end_vertex) != map_vertex_segments.end()) { + if (map_vertex_segments[end_vertex].size() > 1) { + bool flag_non_ele = false; + for (auto sg2 : map_vertex_segments[end_vertex]) { + if (sg2 == sg1) continue; + bool is_shower2 = sg2->flags_any(SegmentFlags::kShowerTrajectory) || + sg2->flags_any(SegmentFlags::kShowerTopology); + if (!is_shower2) flag_non_ele = true; + } + + if (!flag_non_ele && map_vertex_segments[end_vertex].size() <= 3) { + flag_good_track = true; + } + } else { + flag_good_track = true; + } + } + } + } + + (void) n_showers; // n_showers is not used further + + // Count vertex types in shower + int n_multi_vtx = 0; + int n_two_vtx = 0; + std::map vtx_segment_count; + + for (auto edesc : shower->edges()) { + auto seg = shower->view_graph()[edesc].segment; + if (!seg) continue; + + auto seg_edesc = seg->get_descriptor(); + auto source_vdesc = boost::source(seg_edesc, graph); + auto target_vdesc = boost::target(seg_edesc, graph); + VertexPtr v1 = graph[source_vdesc].vertex; + VertexPtr v2 = graph[target_vdesc].vertex; + + if (v1) vtx_segment_count[v1]++; + if (v2) vtx_segment_count[v2]++; + } + + for (auto& [vtx, count] : vtx_segment_count) { + if (count == 2) n_two_vtx++; + else if (count > 2) n_multi_vtx++; + } + + // Apply selection criteria + if (!flag_good_track && n_multi_vtx > 0 && max_seg_length < 65 * units::cm && + ((total_length < n_tracks * 27 * units::cm && total_length < 85 * units::cm) || + (total_length < n_tracks * 18 * units::cm && total_length < 95 * units::cm)) && + n_two_vtx < 3) { + + map_shower_max_sg[shower] = max_sg; + if (shower->get_total_length() > max_length) { + max_shower = shower; + max_length = shower->get_total_length(); + } + } + } + + // Process selected showers + for (auto& [shower, max_sg] : map_shower_max_sg) { + if (shower == max_shower) { + // Convert to EM shower (particle type 11 = electron) + if (shower->start_segment() && shower->start_segment()->has_particle_info() && shower->start_segment()->particle_info()) { + shower->start_segment()->particle_info()->set_pdg(11); + } + + // Set avoid muon check flag on max segment + if (max_sg) { + max_sg->set_flags(SegmentFlags::kAvoidMuonCheck); + } + + // Add shower to collection + showers.insert(shower); + + // Check for showers to delete that conflict with new shower + std::set shower_vertices; + for (auto vdesc : shower->nodes()) { + auto vtx = shower->view_graph()[vdesc].vertex; + if (vtx) shower_vertices.insert(vtx); + } + + for (auto shower1 : showers) { + if (shower == shower1) continue; + auto [start_vtx1, conn_type1] = shower1->get_start_vertex_and_type(); + if (conn_type1 == 1 && start_vtx1 && shower_vertices.find(start_vtx1) != shower_vertices.end()) { + del_showers.insert(shower1); + } + } + } + } + + // Delete conflicting showers + for (auto shower1 : del_showers) { + auto [start_vtx1, conn_type1] = shower1->get_start_vertex_and_type(); + if (start_vtx1 != main_vertex) { + showers.erase(shower1); + } + } + + // Update shower maps + update_shower_maps(showers, map_vertex_in_shower, map_segment_in_shower, map_vertex_to_shower, used_shower_clusters); + } +} + +void PatternAlgorithms::shower_clustering_connecting_to_main_vertex(Graph& graph, VertexPtr main_vertex, std::set& showers, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters){ + if (!main_vertex) return; + + // Build map_vertex_segments from graph + std::map> map_vertex_segments; + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr seg = graph[*eit].segment; + if (!seg) continue; + + auto source_vdesc = boost::source(*eit, graph); + auto target_vdesc = boost::target(*eit, graph); + VertexPtr v1 = graph[source_vdesc].vertex; + VertexPtr v2 = graph[target_vdesc].vertex; + + if (v1) map_vertex_segments[v1].insert(seg); + if (v2) map_vertex_segments[v2].insert(seg); + } + + // Step 1: Collect segments from showers starting at main_vertex + std::set used_segments; + for (auto shower : showers) { + auto [start_vtx, conn_type] = shower->get_start_vertex_and_type(); + if (start_vtx == main_vertex) { + // Collect all segments from this shower + for (auto edesc : shower->edges()) { + auto seg = shower->view_graph()[edesc].segment; + if (seg) { + used_segments.insert(seg); + } + } + + // If there's only 1 segment and it's short, clear used_segments + if (used_segments.size() == 1 && segment_track_length(*used_segments.begin()) < 8 * units::cm) { + used_segments.clear(); + } + } + } + + // Step 2: If used_segments is empty, try to create new showers + if (used_segments.empty()) { + std::set del_showers; + std::map map_shower_max_sg; + ShowerPtr max_shower = nullptr; + double max_length = 0; + + // Get segments connected to main_vertex + auto& main_vtx_segments = map_vertex_segments[main_vertex]; + + for (auto sg : main_vtx_segments) { + // Calculate number of daughter showers for this segment + auto pair_result = calculate_num_daughter_showers(graph, main_vertex, sg, true); + + // Skip if segment is in a shower + if (map_segment_in_shower.find(sg) != map_segment_in_shower.end()) continue; + + // Get segment properties + double medium_dQ_dx = segment_median_dQ_dx(sg); + double medium_dQ_dx_1 = medium_dQ_dx / (43e3 / units::cm); + + // Get particle type if available + int particle_type = 0; + if (sg->has_particle_info() && sg->particle_info()) { + particle_type = sg->particle_info()->pdg(); + } + + // Skip segments with certain particle types and high dQ/dx + if ((particle_type == 11) || + (particle_type == 2212 && ((medium_dQ_dx_1 > 1.45 && pair_result.first <= 3) || medium_dQ_dx_1 > 2.7)) || + (particle_type == 211 && medium_dQ_dx_1 > 2.0)) { + continue; + } + + // Create a new shower + ShowerPtr shower = std::make_shared(graph); + shower->set_start_vertex(main_vertex, 1); + shower->set_start_segment(sg); + shower->complete_structure_with_start_segment(used_segments); + + // Analyze shower structure + int n_tracks = 0; + int n_showers = 0; + double total_length = 0; + double max_seg_length = 0; + SegmentPtr max_sg = nullptr; + bool flag_good_track = false; + + // Iterate through shower segments + for (auto edesc : shower->edges()) { + auto sg1 = shower->view_graph()[edesc].segment; + if (!sg1) continue; + + double length = segment_track_length(sg1); + double medium_dQ_dx_sg = segment_median_dQ_dx(sg1); + double medium_dQ_dx_norm = medium_dQ_dx_sg / (43e3 / units::cm); + + // Check if segment is a shower + bool is_shower = sg1->flags_any(SegmentFlags::kShowerTrajectory) || + sg1->flags_any(SegmentFlags::kShowerTopology); + if (is_shower) n_showers++; + + n_tracks++; + total_length += length; + + if (max_seg_length < length) { + max_seg_length = length; + max_sg = sg1; + } + + // Check for good track properties + if (!sg1->dir_weak() && (length > 3.6 * units::cm || (length > 2.4 * units::cm && medium_dQ_dx_norm > 2.5))) { + // Find end vertex of this segment + auto seg_edesc = sg1->get_descriptor(); + auto source_vdesc = boost::source(seg_edesc, graph); + auto target_vdesc = boost::target(seg_edesc, graph); + VertexPtr v1 = graph[source_vdesc].vertex; + VertexPtr v2 = graph[target_vdesc].vertex; + + // Determine end vertex based on dirsign + // For dirsign == 1, direction is from front to back of fits + // For dirsign == -1, direction is from back to front of fits + VertexPtr end_vertex = nullptr; + if (sg1->dirsign() == 1) { + // End is at back of fits + if (!sg1->fits().empty() && v1 && v2) { + auto& fits = sg1->fits(); + double dist1 = (fits.back().point - (v1->fit().valid() ? v1->fit().point : v1->wcpt().point)).magnitude(); + double dist2 = (fits.back().point - (v2->fit().valid() ? v2->fit().point : v2->wcpt().point)).magnitude(); + end_vertex = (dist1 < dist2) ? v1 : v2; + } + } else if (sg1->dirsign() == -1) { + // End is at front of fits (reversed direction) + if (!sg1->fits().empty() && v1 && v2) { + auto& fits = sg1->fits(); + double dist1 = (fits.front().point - (v1->fit().valid() ? v1->fit().point : v1->wcpt().point)).magnitude(); + double dist2 = (fits.front().point - (v2->fit().valid() ? v2->fit().point : v2->wcpt().point)).magnitude(); + end_vertex = (dist1 < dist2) ? v1 : v2; + } + } + + if (end_vertex && map_vertex_segments.find(end_vertex) != map_vertex_segments.end()) { + if (map_vertex_segments[end_vertex].size() > 1) { + bool flag_non_ele = false; + for (auto sg2 : map_vertex_segments[end_vertex]) { + if (sg2 == sg1) continue; + bool is_shower2 = sg2->flags_any(SegmentFlags::kShowerTrajectory) || + sg2->flags_any(SegmentFlags::kShowerTopology); + if (!is_shower2) flag_non_ele = true; + } + + if (!flag_non_ele && map_vertex_segments[end_vertex].size() <= 3) { + flag_good_track = true; + } + } else { + flag_good_track = true; + } + } + } + } + + (void) n_showers; // n_showers is not used further + + // Count vertex types in shower + int n_multi_vtx = 0; + int n_two_vtx = 0; + std::map vtx_segment_count; + + for (auto edesc : shower->edges()) { + auto seg = shower->view_graph()[edesc].segment; + if (!seg) continue; + + auto seg_edesc = seg->get_descriptor(); + auto source_vdesc = boost::source(seg_edesc, graph); + auto target_vdesc = boost::target(seg_edesc, graph); + VertexPtr v1 = graph[source_vdesc].vertex; + VertexPtr v2 = graph[target_vdesc].vertex; + + if (v1) vtx_segment_count[v1]++; + if (v2) vtx_segment_count[v2]++; + } + + for (auto& [vtx, count] : vtx_segment_count) { + if (count == 2) n_two_vtx++; + else if (count > 2) n_multi_vtx++; + } + + // Apply selection criteria + if (!flag_good_track && n_multi_vtx > 0 && max_seg_length < 65 * units::cm && + ((total_length < n_tracks * 27 * units::cm && total_length < 85 * units::cm) || + (total_length < n_tracks * 18 * units::cm && total_length < 95 * units::cm)) && + n_two_vtx < 3) { + + map_shower_max_sg[shower] = max_sg; + if (shower->get_total_length() > max_length) { + max_shower = shower; + max_length = shower->get_total_length(); + } + } + } + + // Process selected showers + for (auto& [shower, max_sg] : map_shower_max_sg) { + if (shower == max_shower) { + // Convert to EM shower (particle type 11 = electron) + std::cout << "Convert EM shower " << shower->start_segment()->id() << std::endl; + if (shower->start_segment() && shower->start_segment()->has_particle_info() && shower->start_segment()->particle_info()) { + shower->start_segment()->particle_info()->set_pdg(11); + } + + // Set avoid muon check flag on max segment + if (max_sg) { + max_sg->set_flags(SegmentFlags::kAvoidMuonCheck); + } + + // Add shower to collection + showers.insert(shower); + + // Check for showers to delete that conflict with new shower + // Collect all vertices in the new shower + std::set shower_vertices; + for (auto vdesc : shower->nodes()) { + auto vtx = shower->view_graph()[vdesc].vertex; + if (vtx) shower_vertices.insert(vtx); + } + + for (auto shower1 : showers) { + if (shower == shower1) continue; + auto [start_vtx1, conn_type1] = shower1->get_start_vertex_and_type(); + if (conn_type1 == 1 && start_vtx1 && shower_vertices.find(start_vtx1) != shower_vertices.end()) { + del_showers.insert(shower1); + } + } + } + } + + // Delete conflicting showers + for (auto shower1 : del_showers) { + auto [start_vtx1, conn_type1] = shower1->get_start_vertex_and_type(); + if (start_vtx1 != main_vertex) { + showers.erase(shower1); + } + } + + // Update shower maps + update_shower_maps(showers, map_vertex_in_shower, map_segment_in_shower, map_vertex_to_shower, used_shower_clusters); + } +} From 9ac39f1094afef88021b542eb3d4b75429132bf3 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Tue, 6 Jan 2026 19:25:02 -0800 Subject: [PATCH 098/111] add one more function --- clus/inc/WireCellClus/DynamicPointCloud.h | 3 + clus/inc/WireCellClus/NeutrinoPatternBase.h | 3 + clus/src/DynamicPointCloud.cxx | 26 + clus/src/NeutrinoShowerClustering.cxx | 658 ++++++++++++++++++++ clus/src/NeutrinoVertexFinder.cxx | 72 ++- clus/src/PRSegmentFunctions.cxx | 23 +- 6 files changed, 764 insertions(+), 21 deletions(-) diff --git a/clus/inc/WireCellClus/DynamicPointCloud.h b/clus/inc/WireCellClus/DynamicPointCloud.h index ea41ee38..8fd6fdd4 100644 --- a/clus/inc/WireCellClus/DynamicPointCloud.h +++ b/clus/inc/WireCellClus/DynamicPointCloud.h @@ -56,6 +56,8 @@ namespace WireCell::Clus::Facade { const std::unordered_map &kd2d_l2g(const int plane, const int face, const int apa) const; const std::unordered_map> &kd2d_g2l(const int plane, const int face, const int apa) const; + geo_point_t get_center_point_radius(const geo_point_t &p_test, const double radius) const; + /// @brief: kd2d().radius(radius) /// @return: [dist, Cluster, global point_index] std::vector> get_2d_points_info(const geo_point_t &p, @@ -66,6 +68,7 @@ namespace WireCell::Clus::Facade { /// @brief: dist, Cluster, global point_index std::tuple get_closest_2d_point_info(const geo_point_t &p, const int plane, const int face, const int apa) const; + std::pair hough_transform(const geo_point_t &origin, const double dis) const; geo_point_t vhough_transform(const geo_point_t &origin, const double dis) const; diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index c910b3ec..4fb29eb6 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -137,6 +137,9 @@ namespace WireCell::Clus::PR { void update_shower_maps(std::set& showers, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters); void shower_clustering_with_nv_in_main_cluster(Graph& graph, VertexPtr main_vertex, std::set& showers, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, std::set& vertices_in_long_muon, std::set& segments_in_long_muon); void shower_clustering_connecting_to_main_vertex(Graph& graph, VertexPtr main_vertex, std::set& showers, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters); + void shower_clustering_with_nv_from_main_cluster(Graph& graph, VertexPtr main_vertex, Facade::Cluster* main_cluster, std::set& showers, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters); + void shower_clustering_with_nv_from_vertices(Graph& graph, VertexPtr main_vertex, Facade::Cluster* main_cluster, std::vector& other_clusters, std::set& showers, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); + // deghost related functions ... diff --git a/clus/src/DynamicPointCloud.cxx b/clus/src/DynamicPointCloud.cxx index a594cbf9..27889647 100644 --- a/clus/src/DynamicPointCloud.cxx +++ b/clus/src/DynamicPointCloud.cxx @@ -186,6 +186,32 @@ void DynamicPointCloud::add_points(const std::vector &points) { } } +geo_point_t DynamicPointCloud::get_center_point_radius(const geo_point_t &p_test, const double radius) const{ + auto &kd3d = this->kd3d(); + + // Create query point + std::vector query = {p_test.x(), p_test.y(), p_test.z()}; + + // Perform radius search (NFKDVec uses squared distance) + auto results = kd3d.radius(radius * radius, query); + + // Calculate center point + geo_point_t center(0, 0, 0); + int ncount = 0; + + for (const auto &[idx, _] : results) { + const auto &pt = m_points[idx]; + center.set(center.x() + pt.x, center.y() + pt.y, center.z() + pt.z); + ncount++; + } + + if (ncount > 0) { + center.set(center.x() / ncount, center.y() / ncount, center.z() / ncount); + } + + return center; +} + diff --git a/clus/src/NeutrinoShowerClustering.cxx b/clus/src/NeutrinoShowerClustering.cxx index cfff5c62..c73f2e3e 100644 --- a/clus/src/NeutrinoShowerClustering.cxx +++ b/clus/src/NeutrinoShowerClustering.cxx @@ -6,6 +6,20 @@ using namespace WireCell::Clus::PR; using namespace WireCell::Clus; +namespace { + struct cluster_point_info { + Facade::Cluster* cluster; + double min_angle; + double min_dis; + VertexPtr min_vertex; + WireCell::Point min_point; + }; + + bool sortbydis(const cluster_point_info &a, const cluster_point_info &b) { + return (a.min_dis < b.min_dis); + } +} + void PatternAlgorithms::update_shower_maps(std::set& showers, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters){ // Clear all maps map_vertex_to_shower.clear(); @@ -540,3 +554,647 @@ void PatternAlgorithms::shower_clustering_connecting_to_main_vertex(Graph& graph update_shower_maps(showers, map_vertex_in_shower, map_segment_in_shower, map_vertex_to_shower, used_shower_clusters); } } + +void PatternAlgorithms::shower_clustering_with_nv_from_main_cluster(Graph& graph, VertexPtr main_vertex, Facade::Cluster* main_cluster, std::set& showers, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters){ + if (!main_vertex || !main_cluster) return; + + // Build map_segment_vertices from graph + std::map> map_segment_vertices; + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr seg = graph[*eit].segment; + if (!seg) continue; + + auto source_vdesc = boost::source(*eit, graph); + auto target_vdesc = boost::target(*eit, graph); + VertexPtr v1 = graph[source_vdesc].vertex; + VertexPtr v2 = graph[target_vdesc].vertex; + + if (v1) map_segment_vertices[seg].insert(v1); + if (v2) map_segment_vertices[seg].insert(v2); + } + + std::map map_shower_dir; + std::map map_shower_angle_offset; + WireCell::Vector drift_dir(1, 0, 0); + SegmentPtr max_length_segment = nullptr; + + // Step 1: Find the maximum length segment in main_cluster + { + double max_length = 0; + for (auto& [seg, vertices] : map_segment_vertices) { + if (seg->cluster() != main_cluster) continue; + double length = segment_track_length(seg); + if (length > max_length && length > 6 * units::cm) { + max_length = length; + max_length_segment = seg; + } + } + } + + // Step 2: Build map_shower_dir for showers in main_cluster + for (auto& [seg, vertices] : map_segment_vertices) { + if (seg->cluster() != main_cluster) continue; + if (map_segment_in_shower.find(seg) == map_segment_in_shower.end()) continue; + + ShowerPtr shower = map_segment_in_shower[seg]; + + // Skip long muons + int particle_type = 0; + if (shower->start_segment() && shower->start_segment()->has_particle_info() && shower->start_segment()->particle_info()) { + particle_type = shower->start_segment()->particle_info()->pdg(); + } + if (std::abs(particle_type) == 13) continue; + + double total_length = shower->get_total_length(); + + if (seg == shower->start_segment()) { + auto [start_vtx, conn_type] = shower->get_start_vertex_and_type(); + WireCell::Point start_point = start_vtx ? (start_vtx->fit().valid() ? start_vtx->fit().point : start_vtx->wcpt().point) : WireCell::Point(0, 0, 0); + + bool is_shower_topology = seg->flags_any(SegmentFlags::kShowerTopology); + double medium_dQ_dx = segment_median_dQ_dx(seg); + double seg_length = segment_track_length(seg); + + if (is_shower_topology || shower->get_num_segments() > 2 || + (medium_dQ_dx > 43e3 / units::cm * 1.5 && seg_length > 0)) { + if (seg_length > 10 * units::cm) { + WireCell::Vector dir_shower = segment_cal_dir_3vector(seg, start_point, 15 * units::cm); + map_shower_dir[shower] = dir_shower; + } else { + WireCell::Vector dir_shower = shower_cal_dir_3vector(*shower, start_point, 15 * units::cm); + map_shower_dir[shower] = dir_shower; + } + } else if (shower->get_num_segments() <= 2) { + if (total_length > 30 * units::cm) { + if (seg_length > 10 * units::cm) { + WireCell::Vector dir_shower = segment_cal_dir_3vector(seg, start_point, 15 * units::cm); + map_shower_dir[shower] = dir_shower; + } else { + WireCell::Vector dir_shower = shower_cal_dir_3vector(*shower, start_point, 15 * units::cm); + map_shower_dir[shower] = dir_shower; + } + } + } + + // Very large shower + if (total_length > 100 * units::cm) { + WireCell::Vector dir_shower = shower_cal_dir_3vector(*shower, start_point, 60 * units::cm); + map_shower_dir[shower] = dir_shower; + } + + // Max length segment or segment connected to main_vertex + if (seg == max_length_segment && map_shower_dir.find(shower) == map_shower_dir.end()) { + WireCell::Vector dir_shower = shower_cal_dir_3vector(*shower, start_point, 15 * units::cm); + map_shower_dir[shower] = dir_shower; + } else if (map_shower_dir.find(shower) == map_shower_dir.end() && + map_segment_vertices[seg].find(main_vertex) != map_segment_vertices[seg].end()) { + if (seg_length > 5 * units::cm) { + WireCell::Vector dir_shower = shower_cal_dir_3vector(*shower, start_point, 15 * units::cm); + map_shower_dir[shower] = dir_shower; + } + } + + // Check if parallel to drift direction + if (map_shower_dir.find(shower) != map_shower_dir.end()) { + map_shower_angle_offset[shower] = 0; + double angle_to_drift = std::abs(map_shower_dir[shower].dot(drift_dir) / (map_shower_dir[shower].magnitude() * drift_dir.magnitude())); + angle_to_drift = std::acos(std::clamp(angle_to_drift, -1.0, 1.0)) / M_PI * 180.0; + if (std::abs(angle_to_drift - 90) < 5) { + map_shower_dir[shower] = shower_cal_dir_3vector(*shower, start_point, 50 * units::cm); + map_shower_angle_offset[shower] = 5; + } + } + } + } + + // Step 3: If no shower directions found, try to add segments based on closest distance + if (map_shower_dir.empty()) { + std::map map_shower_length; + for (auto shower : showers) { + map_shower_length[shower] = shower->get_total_length(); + } + + bool flag_continue = true; + while (flag_continue) { + flag_continue = false; + for (auto& [seg1, vertices] : map_segment_vertices) { + if (seg1->cluster() == main_cluster) continue; + if (map_segment_in_shower.find(seg1) != map_segment_in_shower.end()) continue; + + double min_dis = 1e9; + ShowerPtr min_shower = nullptr; + + for (auto shower : showers) { + int particle_type = 0; + if (shower->start_segment() && shower->start_segment()->has_particle_info() && shower->start_segment()->particle_info()) { + particle_type = shower->start_segment()->particle_info()->pdg(); + } + if (particle_type == 13) continue; + + if (segment_track_length(seg1) > 0.75 * map_shower_length[shower]) continue; + + double dis = shower_get_closest_dis(*shower, seg1); + if (dis < min_dis) { + min_dis = dis; + min_shower = shower; + } + } + + if (min_shower && min_dis < 3.5 * units::cm) { + min_shower->add_segment(seg1); + map_shower_length[min_shower] = min_shower->get_total_length(); + flag_continue = true; + } + } + update_shower_maps(showers, map_vertex_in_shower, map_segment_in_shower, map_vertex_to_shower, used_shower_clusters); + } + } + + if (map_shower_dir.empty()) return; + + // Step 4: Examine other segments and add to showers based on angle and distance + for (auto& [seg1, vertices] : map_segment_vertices) { + if (seg1->cluster() == main_cluster) continue; + if (map_segment_in_shower.find(seg1) != map_segment_in_shower.end()) continue; + + double min_dis = 1e9; + ShowerPtr min_shower = nullptr; + + for (auto& [shower, dir] : map_shower_dir) { + auto [start_vtx, conn_type] = shower->get_start_vertex_and_type(); + if (!start_vtx) continue; + + WireCell::Point start_point = start_vtx->fit().valid() ? start_vtx->fit().point : start_vtx->wcpt().point; + + // Get closest point on segment to shower start vertex + auto [dist, closest_pt] = segment_get_closest_point(seg1, start_point); + + // Vector from shower start to closest point + WireCell::Vector v1(closest_pt.x() - start_point.x(), + closest_pt.y() - start_point.y(), + closest_pt.z() - start_point.z()); + + double angle = std::acos(std::clamp(dir.dot(v1) / (dir.magnitude() * v1.magnitude()), -1.0, 1.0)); + angle = angle / M_PI * 180.0; + + double angle_offset = 0; + if (map_shower_angle_offset.find(shower) != map_shower_angle_offset.end()) { + angle_offset = map_shower_angle_offset[shower]; + } + + // Check angle and distance criteria + if ((angle < 25.0 + angle_offset && dist < 80 * units::cm) || + (angle < 12.5 + angle_offset * 8 / 5 && dist < 130 * units::cm) || + (angle < 5 + angle_offset * 2 && dist < 200 * units::cm)) { + + double dis = std::pow(dist * std::cos(angle * M_PI / 180.0), 2) / std::pow(40 * units::cm, 2) + + std::pow(dist * std::sin(angle * M_PI / 180.0), 2) / std::pow(5 * units::cm, 2); + + if (dis < min_dis) { + min_dis = dis; + min_shower = shower; + } + } + } + + if (min_shower) { + min_shower->add_segment(seg1); + } + } + + update_shower_maps(showers, map_vertex_in_shower, map_segment_in_shower, map_vertex_to_shower, used_shower_clusters); +} + +void PatternAlgorithms::shower_clustering_with_nv_from_vertices(Graph& graph, VertexPtr main_vertex, Facade::Cluster* main_cluster, std::vector& other_clusters, std::set& showers, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model){ + if (!main_vertex || !main_cluster) return; + + // Build map_cluster_segments and map_segment_cluster + std::map> map_cluster_segments; + std::map map_segment_cluster; + std::map> map_segment_vertices; + + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr seg = graph[*eit].segment; + if (!seg || !seg->cluster()) continue; + + map_cluster_segments[seg->cluster()].push_back(seg); + map_segment_cluster[seg] = seg->cluster(); + + auto source_vdesc = boost::source(*eit, graph); + auto target_vdesc = boost::target(*eit, graph); + VertexPtr v1 = graph[source_vdesc].vertex; + VertexPtr v2 = graph[target_vdesc].vertex; + + if (v1) map_segment_vertices[seg].insert(v1); + if (v2) map_segment_vertices[seg].insert(v2); + } + + // Step 1: Build map_cluster_center_point + std::map> map_cluster_center_point; + + for (auto cluster : other_clusters) { + auto it1 = map_cluster_segments.find(cluster); + if (it1 == map_cluster_segments.end()) continue; + + double acc_length = 0; + double acc_length1 = 0; + WireCell::Point p(0, 0, 0); + int np = 0; + + for (auto seg : it1->second) { + if (map_segment_in_shower.find(seg) != map_segment_in_shower.end()) continue; + + bool is_shower = seg->flags_any(SegmentFlags::kShowerTrajectory) || seg->flags_any(SegmentFlags::kShowerTopology); + int particle_type = 0; + if (seg->has_particle_info() && seg->particle_info()) { + particle_type = seg->particle_info()->pdg(); + } + + if (is_shower || particle_type == 0 || + ((std::abs(particle_type) == 13 || std::abs(particle_type) == 211) && seg->dir_weak())) { + double length = segment_track_length(seg); + acc_length += length; + + const auto& fits = seg->fits(); + for (const auto& fit : fits) { + p.set(p.x() + fit.point.x(), p.y() + fit.point.y(), p.z() + fit.point.z()); + np++; + } + } + + if (particle_type != 11 && !is_shower) { + acc_length1 += segment_track_length(seg); + } + } + + if ((acc_length > 1.0 * units::cm && acc_length >= acc_length1) || acc_length > 10 * units::cm) { + if (np > 0) { + p.set(p.x() / np, p.y() / np, p.z() / np); + } + map_cluster_center_point[cluster] = std::make_pair(p, acc_length); + } + } + + // Step 2: List main cluster vertices + std::vector main_cluster_vertices; + auto [vbegin, vend] = boost::vertices(graph); + for (auto vit = vbegin; vit != vend; ++vit) { + VertexPtr vtx = graph[*vit].vertex; + if (!vtx || !vtx->cluster() || vtx->cluster() != main_cluster) continue; + + if (vtx != main_vertex) { + if (vertices_in_long_muon.find(vtx) != vertices_in_long_muon.end()) continue; + if (map_vertex_in_shower.find(vtx) != map_vertex_in_shower.end()) continue; + } + main_cluster_vertices.push_back(vtx); + } + + // Step 3: Analyze each cluster against main cluster vertices + std::map map_cluster_pi; + + // Get wpid_params for point cloud creation + auto* grouping = main_cluster->grouping(); + if (!grouping) return; + const auto& wpids = grouping->wpids(); + std::map> wpid_params; + std::map> wpid_U_dir, wpid_V_dir, wpid_W_dir; + std::set apas; + Facade::compute_wireplane_params(wpids, dv, wpid_params, wpid_U_dir, wpid_V_dir, wpid_W_dir, apas); + + for (auto& [cluster, center_pair] : map_cluster_center_point) { + WireCell::Point center_p = center_pair.first; + + // Create point cloud from cluster segments + std::vector> point_plane_pairs; + for (auto seg : map_cluster_segments[cluster]) { + for (const auto& fit : seg->fits()) { + WirePlaneId wpid = dv->contained_by(fit.point); + point_plane_pairs.emplace_back(fit.point, wpid); + } + } + auto dpc_points = Facade::make_points_direct(cluster, dv, wpid_params, point_plane_pairs, false); + auto pcloud = std::make_shared(wpid_params); + pcloud->add_points(dpc_points); + + cluster_point_info min_pi; + min_pi.cluster = cluster; + min_pi.min_angle = 90; + min_pi.min_dis = 1e9; + min_pi.min_vertex = nullptr; + + cluster_point_info main_pi; + main_pi.cluster = cluster; + main_pi.min_vertex = main_vertex; + + for (auto vtx : main_cluster_vertices) { + WireCell::Point vtx_pt = vtx->fit().valid() ? vtx->fit().point : vtx->wcpt().point; + + // Get closest point using KD-tree + auto& kd3d = pcloud->kd3d(); + std::vector query = {vtx_pt.x(), vtx_pt.y(), vtx_pt.z()}; + auto results = kd3d.knn(1, query); + + if (results.empty()) continue; + + const size_t idx = results[0].first; + const double dis = std::sqrt(results[0].second); // KD-tree returns squared distance + const auto& closest_pt_data = pcloud->get_points()[idx]; + WireCell::Point closest_pt(closest_pt_data.x, closest_pt_data.y, closest_pt_data.z); + + WireCell::Vector v1(closest_pt.x() - vtx_pt.x(), + closest_pt.y() - vtx_pt.y(), + closest_pt.z() - vtx_pt.z()); + WireCell::Vector v2(center_p.x() - closest_pt.x(), + center_p.y() - closest_pt.y(), + center_p.z() - closest_pt.z()); + + WireCell::Point near_center = pcloud->get_center_point_radius(closest_pt, 2 * units::cm); + WireCell::Vector v3(near_center.x() - closest_pt.x(), + near_center.y() - closest_pt.y(), + near_center.z() - closest_pt.z()); + + double angle = std::acos(std::clamp(v1.dot(v2) / (v1.magnitude() * v2.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + double angle3 = std::acos(std::clamp(v1.dot(v3) / (v1.magnitude() * v3.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + + if (angle < 30 || (dis < 5 * units::cm && angle < 45)) { + angle = std::min(angle, angle3); + } + + if (angle < 7.5) { + if (dis * std::sin(angle / 180.0 * M_PI) < min_pi.min_dis * std::sin(min_pi.min_angle / 180.0 * M_PI) && angle < 90) { + min_pi.min_angle = angle; + min_pi.min_dis = dis; + min_pi.min_vertex = vtx; + min_pi.min_point = closest_pt; + } + } else { + if (angle < min_pi.min_angle) { + min_pi.min_angle = angle; + min_pi.min_dis = dis; + min_pi.min_vertex = vtx; + min_pi.min_point = closest_pt; + } + } + + if (vtx == main_vertex) { + main_pi.min_angle = angle; + main_pi.min_dis = dis; + main_pi.min_point = closest_pt; + } + } + + if (!min_pi.min_vertex) { + min_pi.min_angle = main_pi.min_angle; + min_pi.min_vertex = main_vertex; + min_pi.min_point = main_pi.min_point; + min_pi.min_dis = main_pi.min_dis; + } + + WireCell::Point main_vtx_pt = main_vertex->fit().valid() ? main_vertex->fit().point : main_vertex->wcpt().point; + WireCell::Point min_vtx_pt = min_pi.min_vertex->fit().valid() ? min_pi.min_vertex->fit().point : min_pi.min_vertex->wcpt().point; + double vtx_dis = (main_vtx_pt - min_vtx_pt).magnitude(); + + if (main_pi.min_angle < min_pi.min_angle + 3 && main_pi.min_dis < min_pi.min_dis * 1.2 && + (min_pi.min_angle > 0.9 * main_pi.min_angle || vtx_dis < 1.5 * units::cm)) { + map_cluster_pi[cluster] = main_pi; + } else { + map_cluster_pi[cluster] = min_pi; + } + } + + // Step 4: Sort by distance + std::vector vec_pi; + for (auto& [cluster, pi] : map_cluster_pi) { + vec_pi.push_back(pi); + } + std::sort(vec_pi.begin(), vec_pi.end(), sortbydis); + + std::map map_cluster_associated_vertex; + for (const auto& pi : vec_pi) { + if (pi.min_angle < 10) { + map_cluster_associated_vertex[pi.cluster] = pi.min_vertex; + } + } + + // Step 5: Process each cluster + for (const auto& pi : vec_pi) { + Facade::Cluster* cluster = pi.cluster; + VertexPtr vertex = pi.min_vertex; + WireCell::Point point = pi.min_point; + SegmentPtr sg1 = nullptr; + double angle = pi.min_angle; + + if (angle > 50 && pi.min_dis > 6 * units::cm) continue; + if (angle > 60) continue; + + // Find segment at the point + for (auto seg : map_cluster_segments[cluster]) { + if (map_segment_in_shower.find(seg) != map_segment_in_shower.end()) continue; + auto [dis, closest_pt] = segment_get_closest_point(seg, point); + if (dis < 0.01 * units::cm) { + sg1 = seg; + break; + } + } + + if (!sg1) continue; + + // Create new shower + ShowerPtr shower = std::make_shared(graph); + shower->set_start_vertex(vertex, 2); + showers.insert(shower); + + // Check if point is at segment endpoint + const auto& fits = sg1->fits(); + if (!fits.empty()) { + double dist_front = (fits.front().point - point).magnitude(); + double dist_back = (fits.back().point - point).magnitude(); + + if (dist_front < 0.01 * units::cm || dist_back < 0.01 * units::cm) { + shower->set_start_segment(sg1, true); + } else { + // Break segment at point + auto [success, seg_pair, new_vtx] = break_segment(graph, sg1, point, particle_data, recomb_model, dv); + + if (!success || !new_vtx) { + shower->set_start_segment(sg1); + } else { + // Determine which segment to use based on direction to vertex + WireCell::Point vtx_pt = vertex->fit().valid() ? vertex->fit().point : vertex->wcpt().point; + WireCell::Vector v3(point.x() - vtx_pt.x(), point.y() - vtx_pt.y(), point.z() - vtx_pt.z()); + + WireCell::Vector v1 = segment_cal_dir_3vector(seg_pair.first, point, 5 * units::cm); + WireCell::Vector v2 = segment_cal_dir_3vector(seg_pair.second, point, 5 * units::cm); + + double angle1 = std::acos(std::clamp(v1.dot(v3) / (v1.magnitude() * v3.magnitude()), -1.0, 1.0)); + double angle2 = std::acos(std::clamp(v2.dot(v3) / (v2.magnitude() * v3.magnitude()), -1.0, 1.0)); + + if (angle1 < angle2) { + shower->set_start_segment(seg_pair.first); + } else { + shower->set_start_segment(seg_pair.second); + } + } + } + } else { + shower->set_start_segment(sg1); + } + + // Set direction based on vertex proximity + auto start_seg = shower->start_segment(); + if (start_seg) { + const auto& seg_fits = start_seg->fits(); + if (!seg_fits.empty()) { + WireCell::Point vtx_pt = vertex->fit().valid() ? vertex->fit().point : vertex->wcpt().point; + double dis1 = (vtx_pt - seg_fits.front().point).magnitude(); + double dis2 = (vtx_pt - seg_fits.back().point).magnitude(); + + if (dis1 < dis2) { + start_seg->dirsign(1); + } else { + start_seg->dirsign(-1); + } + } + + // Set particle type to electron if needed + int pdg = 0; + if (start_seg->has_particle_info() && start_seg->particle_info()) { + pdg = start_seg->particle_info()->pdg(); + } + if (pdg == 0 || std::abs(pdg) == 13) { + auto four_momentum = segment_cal_4mom(start_seg, 11, particle_data, recomb_model); + + // Create ParticleInfo for electron + auto pinfo = std::make_shared( + 11, // electron PDG + particle_data->get_particle_mass(11), // electron mass + particle_data->pdg_to_name(11), // "electron" + four_momentum // 4-momentum + ); + + // Store particle info in start_segment + start_seg->particle_info(pinfo); + } + } + + // Complete shower structure + std::set used_segments; + shower->complete_structure_with_start_segment(used_segments); + + // Calculate shower direction + auto [start_vtx, conn_type] = shower->get_start_vertex_and_type(); + WireCell::Point start_pt = start_vtx ? (start_vtx->fit().valid() ? start_vtx->fit().point : start_vtx->wcpt().point) : point; + + WireCell::Vector dir_shower = segment_cal_dir_3vector(shower->start_segment(), point, 15 * units::cm); + WireCell::Vector dir_main(point.x() - start_pt.x(), point.y() - start_pt.y(), point.z() - start_pt.z()); + + if (std::acos(std::clamp(dir_shower.dot(dir_main) / (dir_shower.magnitude() * dir_main.magnitude()), -1.0, 1.0)) / M_PI * 180.0 > 30) { + auto [_, test_p] = shower_get_closest_point(*shower, start_pt); + dir_shower = shower_cal_dir_3vector(*shower, test_p, 30 * units::cm); + } + if (dir_shower.magnitude() < 0.001) dir_shower = dir_main; + + // Add segments from other clusters + for (auto& [seg1, vertices] : map_segment_vertices) { + if (seg1->cluster() == main_cluster) continue; + if (map_segment_in_shower.find(seg1) != map_segment_in_shower.end()) continue; + if (seg1->cluster() == shower->start_segment()->cluster()) continue; + + auto it1 = map_cluster_associated_vertex.find(map_segment_cluster[seg1]); + + auto [pair_dis, pair_point] = segment_get_closest_point(seg1, start_pt); + WireCell::Vector v1(pair_point.x() - start_pt.x(), pair_point.y() - start_pt.y(), pair_point.z() - start_pt.z()); + WireCell::Vector v2(pair_point.x() - point.x(), pair_point.y() - point.y(), pair_point.z() - point.z()); + + double angle_v1 = std::acos(std::clamp(dir_shower.dot(v1) / (dir_shower.magnitude() * v1.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + double angle_v2 = std::acos(std::clamp(dir_shower.dot(v2) / (dir_shower.magnitude() * v2.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + + const auto& seg1_fits = seg1->fits(); + double tmp_shower_dis = !seg1_fits.empty() ? (shower->start_segment()->fits().front().point - seg1_fits.front().point).magnitude() : 1e9; + double close_shower_dis = shower_get_closest_dis(*shower, seg1); + + if (angle_v2 > 30) continue; + + if ((angle_v1 < 25 && (pair_dis < 80 * units::cm || close_shower_dis < 25 * units::cm)) || + (angle_v2 < 25 && (tmp_shower_dis < 40 * units::cm || close_shower_dis < 25 * units::cm)) || + (angle_v1 < 12.5 && (pair_dis < 120 * units::cm || close_shower_dis < 40 * units::cm)) || + (angle_v2 < 12.5 && (tmp_shower_dis < 80 * units::cm || close_shower_dis < 40 * units::cm))) { + + if (it1 != map_cluster_associated_vertex.end() && seg1->cluster() != shower->start_segment()->cluster()) { + if (it1->second != vertex) { + double dis1 = shower_get_dis(*shower, seg1); + if (dis1 > 25 * units::cm && dis1 > pair_dis * 0.4) { + continue; + } + } + } + shower->add_segment(seg1); + } + } + + // Update particle type + shower->update_particle_type(particle_data, recomb_model); + + bool tmp_flag = (shower->start_vertex() == main_vertex); + std::cout << "Separated shower: " << shower->start_segment()->cluster()->get_cluster_id() * 1000 + shower->start_segment()->id() + << " " << (shower->start_segment()->has_particle_info() && shower->start_segment()->particle_info() ? shower->start_segment()->particle_info()->pdg() : 0) + << " " << shower->get_num_segments() << " " << tmp_flag << " " << pi.min_dis / units::cm << std::endl; + + update_shower_maps(showers, map_vertex_in_shower, map_segment_in_shower, map_vertex_to_shower, used_shower_clusters); + + // Iteratively add segments based on distance + { + std::map map_shower_length; + std::map map_shower_dir; + + for (auto shower1 : showers) { + map_shower_length[shower1] = shower1->get_total_length(); + auto [start_vtx1, _] = shower1->get_start_vertex_and_type(); + WireCell::Point start_pt1 = start_vtx1 ? (start_vtx1->fit().valid() ? start_vtx1->fit().point : start_vtx1->wcpt().point) : WireCell::Point(0, 0, 0); + auto [__, test_p] = shower_get_closest_point(*shower1, start_pt1); + map_shower_dir[shower1] = shower_cal_dir_3vector(*shower1, test_p, 30 * units::cm); + } + + bool flag_continue = true; + while (flag_continue) { + flag_continue = false; + for (auto& [seg1, vertices] : map_segment_vertices) { + if (seg1->cluster() == main_cluster) continue; + if (map_segment_in_shower.find(seg1) != map_segment_in_shower.end()) continue; + + double min_dis = 1e9; + ShowerPtr min_shower = nullptr; + + for (auto shower1 : showers) { + if (segment_track_length(seg1) > 0.75 * map_shower_length[shower1]) continue; + + auto [start_vtx1, _] = shower1->get_start_vertex_and_type(); + WireCell::Point start_point1 = start_vtx1 ? (start_vtx1->fit().valid() ? start_vtx1->fit().point : start_vtx1->wcpt().point) : WireCell::Point(0, 0, 0); + auto [__, test_p] = segment_get_closest_point(seg1, start_point1); + auto [___, test_p1] = shower_get_closest_point(*shower1, start_point1); + + WireCell::Vector tmp_dir(test_p.x() - test_p1.x(), test_p.y() - test_p1.y(), test_p.z() - test_p1.z()); + double angle = std::acos(std::clamp(tmp_dir.dot(map_shower_dir[shower1]) / (tmp_dir.magnitude() * map_shower_dir[shower1].magnitude()), -1.0, 1.0)) / M_PI * 180.0; + + double dis = shower_get_closest_dis(*shower1, seg1); + if (dis < min_dis && angle < 45) { + min_dis = dis; + min_shower = shower1; + } + } + + if (min_shower && min_dis < 3.5 * units::cm) { + min_shower->add_segment(seg1); + map_shower_length[min_shower] = min_shower->get_total_length(); + flag_continue = true; + } + } + update_shower_maps(showers, map_vertex_in_shower, map_segment_in_shower, map_vertex_to_shower, used_shower_clusters); + } + } + } + + std::cout << "With separated-cluster shower: " << showers.size() << std::endl; +} \ No newline at end of file diff --git a/clus/src/NeutrinoVertexFinder.cxx b/clus/src/NeutrinoVertexFinder.cxx index ec4bb1d8..8f8e82ec 100644 --- a/clus/src/NeutrinoVertexFinder.cxx +++ b/clus/src/NeutrinoVertexFinder.cxx @@ -1109,12 +1109,18 @@ bool PatternAlgorithms::examine_direction(Graph& graph, VertexPtr vertex, Vertex // Determine particle type if (flag_shower_in && current_sg->dirsign() == 0 && !is_shower) { - segment_cal_4mom(current_sg, 11, particle_data, recomb_model); + auto four_momentum = segment_cal_4mom(current_sg, 11, particle_data, recomb_model); + auto pinfo = std::make_shared(11, particle_data->get_particle_mass(11), particle_data->pdg_to_name(11), four_momentum); + current_sg->particle_info(pinfo); } else if (flag_shower_in && length < 2.0*units::cm && !is_shower) { - segment_cal_4mom(current_sg, 11, particle_data, recomb_model); + auto four_momentum = segment_cal_4mom(current_sg, 11, particle_data, recomb_model); + auto pinfo = std::make_shared(11, particle_data->get_particle_mass(11), particle_data->pdg_to_name(11), four_momentum); + current_sg->particle_info(pinfo); } else if (flag_shower_in && current_sg->has_particle_info() && (std::abs(current_sg->particle_info()->pdg()) == 13 || current_sg->particle_info()->pdg() == 0)) { - segment_cal_4mom(current_sg, 11, particle_data, recomb_model); + auto four_momentum = segment_cal_4mom(current_sg, 11, particle_data, recomb_model); + auto pinfo = std::make_shared(11, particle_data->get_particle_mass(11), particle_data->pdg_to_name(11), four_momentum); + current_sg->particle_info(pinfo); } else { auto pair_result = calculate_num_daughter_showers(graph, prev_vtx, current_sg); auto pair_result1 = calculate_num_daughter_showers(graph, prev_vtx, current_sg, false); @@ -1165,7 +1171,9 @@ bool PatternAlgorithms::examine_direction(Graph& graph, VertexPtr vertex, Vertex } if (flag_change) { - segment_cal_4mom(current_sg, 11, particle_data, recomb_model); + auto four_momentum = segment_cal_4mom(current_sg, 11, particle_data, recomb_model); + auto pinfo = std::make_shared(11, particle_data->get_particle_mass(11), particle_data->pdg_to_name(11), four_momentum); + current_sg->particle_info(pinfo); } } else if (current_pdg == 11 && num_daughter_showers <= 2 && !flag_shower_in && !current_sg->flags_any(SegmentFlags::kShowerTopology) && @@ -1175,14 +1183,18 @@ bool PatternAlgorithms::examine_direction(Graph& graph, VertexPtr vertex, Vertex double direct_length = segment_track_direct_length(current_sg); if (direct_length >= 34*units::cm || (direct_length < 34*units::cm && direct_length > 0.93 * length)) { - segment_cal_4mom(current_sg, 13, particle_data, recomb_model); + auto four_momentum = segment_cal_4mom(current_sg, 13, particle_data, recomb_model); + auto pinfo = std::make_shared(13, particle_data->get_particle_mass(13), particle_data->pdg_to_name(13), four_momentum); + current_sg->particle_info(pinfo); } } else if (current_pdg == 11 && current_sg->flags_any(SegmentFlags::kShowerTrajectory) && num_daughter_showers == 1 && !flag_only_showers) { auto pair_result1 = calculate_num_daughter_showers(graph, prev_vtx, current_sg, false); if (pair_result1.second > 3*length && pair_result1.second - length > 12*units::cm) { current_sg->unset_flags(SegmentFlags::kShowerTrajectory); - segment_cal_4mom(current_sg, 13, particle_data, recomb_model); + auto four_momentum = segment_cal_4mom(current_sg, 13, particle_data, recomb_model); + auto pinfo = std::make_shared(13, particle_data->get_particle_mass(13), particle_data->pdg_to_name(13), four_momentum); + current_sg->particle_info(pinfo); } } } @@ -1192,13 +1204,19 @@ bool PatternAlgorithms::examine_direction(Graph& graph, VertexPtr vertex, Vertex int current_pdg = current_sg->has_particle_info() ? current_sg->particle_info()->pdg() : 0; if (current_pdg == 0 && !is_shower) { if (flag_only_showers) { - segment_cal_4mom(current_sg, 11, particle_data, recomb_model); + auto four_momentum = segment_cal_4mom(current_sg, 11, particle_data, recomb_model); + auto pinfo = std::make_shared(11, particle_data->get_particle_mass(11), particle_data->pdg_to_name(11), four_momentum); + current_sg->particle_info(pinfo); } else { double dqdx_ratio = segment_median_dQ_dx(current_sg) / (43e3 / units::cm); if (dqdx_ratio > 1.4) { - segment_cal_4mom(current_sg, 2212, particle_data, recomb_model); + auto four_momentum = segment_cal_4mom(current_sg, 2212, particle_data, recomb_model); + auto pinfo = std::make_shared(2212, particle_data->get_particle_mass(2212), particle_data->pdg_to_name(2212), four_momentum); + current_sg->particle_info(pinfo); } else { - segment_cal_4mom(current_sg, 13, particle_data, recomb_model); + auto four_momentum = segment_cal_4mom(current_sg, 13, particle_data, recomb_model); + auto pinfo = std::make_shared(13, particle_data->get_particle_mass(13), particle_data->pdg_to_name(13), four_momentum); + current_sg->particle_info(pinfo); } } } @@ -1216,9 +1234,13 @@ bool PatternAlgorithms::examine_direction(Graph& graph, VertexPtr vertex, Vertex for (auto in_shower : in_showers) { double dqdx_ratio = segment_median_dQ_dx(in_shower) / (43e3 / units::cm); if (dqdx_ratio > 1.3) { - segment_cal_4mom(in_shower, 2212, particle_data, recomb_model); + auto four_momentum = segment_cal_4mom(in_shower, 2212, particle_data, recomb_model); + auto pinfo = std::make_shared(2212, particle_data->get_particle_mass(2212), particle_data->pdg_to_name(2212), four_momentum); + in_shower->particle_info(pinfo); } else { - segment_cal_4mom(in_shower, 211, particle_data, recomb_model); + auto four_momentum = segment_cal_4mom(in_shower, 211, particle_data, recomb_model); + auto pinfo = std::make_shared(211, particle_data->get_particle_mass(211), particle_data->pdg_to_name(211), four_momentum); + in_shower->particle_info(pinfo); } in_shower->unset_flags(SegmentFlags::kShowerTrajectory); in_shower->unset_flags(SegmentFlags::kShowerTopology); @@ -1291,7 +1313,9 @@ bool PatternAlgorithms::examine_direction(Graph& graph, VertexPtr vertex, Vertex if (total_length > 45*units::cm && max_length > 35*units::cm && acc_segments.size() > 1) { for (auto acc_seg : acc_segments) { - segment_cal_4mom(acc_seg, 13, particle_data, recomb_model); + auto four_momentum = segment_cal_4mom(acc_seg, 13, particle_data, recomb_model); + auto pinfo = std::make_shared(13, particle_data->get_particle_mass(13), particle_data->pdg_to_name(13), four_momentum); + acc_seg->particle_info(pinfo); acc_seg->unset_flags(SegmentFlags::kShowerTrajectory); acc_seg->unset_flags(SegmentFlags::kShowerTopology); segments_in_long_muon.insert(acc_seg); @@ -1358,9 +1382,13 @@ bool PatternAlgorithms::examine_direction(Graph& graph, VertexPtr vertex, Vertex if (n_proton > 0) { double dqdx_ratio = segment_median_dQ_dx(sg) / (43e3 / units::cm); if (dqdx_ratio > 1.3) { - segment_cal_4mom(sg, 2212, particle_data, recomb_model); + auto four_momentum = segment_cal_4mom(sg, 2212, particle_data, recomb_model); + auto pinfo = std::make_shared(2212, particle_data->get_particle_mass(2212), particle_data->pdg_to_name(2212), four_momentum); + sg->particle_info(pinfo); } else { - segment_cal_4mom(sg, 211, particle_data, recomb_model); + auto four_momentum = segment_cal_4mom(sg, 211, particle_data, recomb_model); + auto pinfo = std::make_shared(211, particle_data->get_particle_mass(211), particle_data->pdg_to_name(211), four_momentum); + sg->particle_info(pinfo); } } } @@ -1369,7 +1397,9 @@ bool PatternAlgorithms::examine_direction(Graph& graph, VertexPtr vertex, Vertex // Convert non-muon candidates to pions for (auto pion_sg : pion_sgs) { if (pion_sg == muon_sg) continue; - segment_cal_4mom(pion_sg, 211, particle_data, recomb_model); + auto four_momentum = segment_cal_4mom(pion_sg, 211, particle_data, recomb_model); + auto pinfo = std::make_shared(211, particle_data->get_particle_mass(211), particle_data->pdg_to_name(211), four_momentum); + pion_sg->particle_info(pinfo); } } @@ -1387,7 +1417,9 @@ bool PatternAlgorithms::examine_direction(Graph& graph, VertexPtr vertex, Vertex if (!sg->dir_weak()) { // Strong direction - calculate 4-momentum int pdg = sg->particle_info()->pdg(); - segment_cal_4mom(sg, pdg, particle_data, recomb_model); + auto four_momentum = segment_cal_4mom(sg, pdg, particle_data, recomb_model); + auto pinfo = std::make_shared(pdg, particle_data->get_particle_mass(pdg), particle_data->pdg_to_name(pdg), four_momentum); + sg->particle_info(pinfo); } else { // Weak direction - need to check endpoint conditions // Find the two vertices of this segment @@ -1491,7 +1523,9 @@ bool PatternAlgorithms::examine_direction(Graph& graph, VertexPtr vertex, Vertex if (should_calc) { int pdg = sg->particle_info()->pdg(); - segment_cal_4mom(sg, pdg, particle_data, recomb_model); + auto four_momentum = segment_cal_4mom(sg, pdg, particle_data, recomb_model); + auto pinfo = std::make_shared(pdg, particle_data->get_particle_mass(pdg), particle_data->pdg_to_name(pdg), four_momentum); + sg->particle_info(pinfo); } } } @@ -3005,7 +3039,9 @@ VertexPtr PatternAlgorithms::determine_overall_main_vertex(Graph& graph, std::ma sg->particle_info()->set_mass(particle_data->get_particle_mass(2212)); // Calculate 4-momentum - segment_cal_4mom(sg, 2212, particle_data, recomb_model); + auto four_momentum = segment_cal_4mom(sg, 2212, particle_data, recomb_model); + auto pinfo = std::make_shared(2212, particle_data->get_particle_mass(2212), particle_data->pdg_to_name(2212), four_momentum); + sg->particle_info(pinfo); } } } diff --git a/clus/src/PRSegmentFunctions.cxx b/clus/src/PRSegmentFunctions.cxx index 74891c6b..b28aaeea 100644 --- a/clus/src/PRSegmentFunctions.cxx +++ b/clus/src/PRSegmentFunctions.cxx @@ -479,9 +479,26 @@ namespace WireCell::Clus::PR { if (seg->has_particle_info()) { - // Copy particle info if it exists - segment_cal_4mom(seg1, seg->particle_info()->pdg(), particle_data, recomb_model); - segment_cal_4mom(seg2, seg->particle_info()->pdg(), particle_data, recomb_model); + // Copy particle info with 4-momentum for seg1 + int pdg = seg->particle_info()->pdg(); + auto four_momentum1 = segment_cal_4mom(seg1, pdg, particle_data, recomb_model); + auto pinfo1 = std::make_shared( + pdg, + particle_data->get_particle_mass(pdg), + particle_data->pdg_to_name(pdg), + four_momentum1 + ); + seg1->particle_info(pinfo1); + + // Copy particle info with 4-momentum for seg2 + auto four_momentum2 = segment_cal_4mom(seg2, pdg, particle_data, recomb_model); + auto pinfo2 = std::make_shared( + pdg, + particle_data->get_particle_mass(pdg), + particle_data->pdg_to_name(pdg), + four_momentum2 + ); + seg2->particle_info(pinfo2); } // Copy dynamic point clouds if they exist From 1675fa498628fcb51ddf0cef56a7ff72aeff75c7 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Wed, 7 Jan 2026 10:29:04 -0800 Subject: [PATCH 099/111] update prepare for kinematics calculation --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 3 +- clus/inc/WireCellClus/TrackFitting.h | 6 ++ clus/src/NeutrinoEnergyReco.cxx | 94 +++++++++++++++++++++ 3 files changed, 102 insertions(+), 1 deletion(-) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 4fb29eb6..38c2f332 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -149,6 +149,7 @@ namespace WireCell::Clus::PR { void deghost_segments(Graph& graph, std::map map_cluster_main_vertices, std::vector& all_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); void deghosting(Graph& graph, std::map map_cluster_main_vertices, std::vector& all_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv ); - + // energy calculation ... + double cal_corr_factor(WireCell::Point& pt, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); }; } diff --git a/clus/inc/WireCellClus/TrackFitting.h b/clus/inc/WireCellClus/TrackFitting.h index 49fd9706..5c1ffa49 100644 --- a/clus/inc/WireCellClus/TrackFitting.h +++ b/clus/inc/WireCellClus/TrackFitting.h @@ -357,6 +357,12 @@ namespace WireCell::Clus { */ std::map get_all_anodes() const; + /** + * Get the grouping associated with this TrackFitting instance + * @return Pointer to Grouping, or nullptr if not set + */ + Facade::Grouping* grouping() const { return m_grouping; } + /** * Get channel number for a specific wire location * Uses hybrid caching for optimal performance diff --git a/clus/src/NeutrinoEnergyReco.cxx b/clus/src/NeutrinoEnergyReco.cxx index e69de29b..270db6a6 100644 --- a/clus/src/NeutrinoEnergyReco.cxx +++ b/clus/src/NeutrinoEnergyReco.cxx @@ -0,0 +1,94 @@ +#include "WireCellClus/NeutrinoPatternBase.h" +#include "WireCellClus/PRSegmentFunctions.h" +#include "WireCellClus/PRShowerFunctions.h" + +using namespace WireCell::Clus::PR; +using namespace WireCell::Clus; + + +double PatternAlgorithms::cal_corr_factor(WireCell::Point& pt, TrackFitting& track_fitter, IDetectorVolumes::pointer dv){ + double corr_factor = 1.0; + // So far this is an empty class that needs to be filled with actual logic ... + + // Example 1: Find APA and face using detector volumes + // The WirePlaneId contains apa and face information + WirePlaneId wpid = dv->contained_by(pt); + int apa = wpid.apa(); + int face = wpid.face(); + int plane = wpid.index(); // 0=U, 1=V, 2=W + + // std::cout << "Point at x=" << pt.x()/units::cm << " y=" << pt.y()/units::cm + // << " z=" << pt.z()/units::cm << " cm" << std::endl; + // std::cout << " APA=" << apa << " Face=" << face << " Plane=" << plane << std::endl; + + // Example 2: Find the grouping from track_fitter + // The track_fitter contains a reference to the grouping + auto grouping = track_fitter.grouping(); + + // if (grouping) { + // std::cout << " Found grouping with " << grouping->children().size() << " clusters" << std::endl; + + // // Example: Access detector parameters from grouping cache + // double tick = grouping->get_tick().at(apa).at(face); + // double time_offset = grouping->get_time_offset().at(apa).at(face); + // double drift_speed = grouping->get_drift_speed().at(apa).at(face); + + // std::cout << " Tick=" << tick/units::us << " us, " + // << "TimeOffset=" << time_offset/units::us << " us, " + // << "DriftSpeed=" << drift_speed/(units::mm/units::us) << " mm/us" << std::endl; + + // // Example: Access channel information for a wire index + // int wire_index = 100; // example wire index + // WirePlaneLayer_t layer = static_cast(plane); + + // try { + // auto channel = grouping->get_plane_channel_wind(apa, face, layer, wire_index); + // int channel_ident = channel->ident(); + // std::cout << " Wire " << wire_index << " -> Channel " << channel_ident << std::endl; + // } catch (...) { + // std::cout << " Wire " << wire_index << " not found in this plane" << std::endl; + // } + + // // Example: Access charge data in a region around the point + // // Convert 3D point to time slice and wire index + // auto [time_slice, wire_idx] = grouping->convert_3Dpoint_time_ch(pt, apa, face, plane); + // std::cout << " 3D point converts to: time_slice=" << time_slice + // << ", wire_index=" << wire_idx << std::endl; + + // // Get charge data in a small window around this point + // int time_window = 5; // +/- 5 time slices + // int wire_window = 3; // +/- 3 wires + + // auto charge_map = grouping->get_overlap_good_ch_charge( + // time_slice - time_window, time_slice + time_window, + // wire_idx - wire_window, wire_idx + wire_window, + // apa, face, plane + // ); + + // std::cout << " Found " << charge_map.size() << " charge measurements nearby" << std::endl; + + // // Example: Iterate through charge measurements + // double total_charge = 0; + // for (const auto& [key, value] : charge_map) { + // int t_slice = key.first; + // int w_idx = key.second; + // double charge = value.first; + // double uncertainty = value.second; + // total_charge += charge; + + // // Optionally print details + // // std::cout << " t=" << t_slice << " w=" << w_idx + // // << " Q=" << charge << " +/- " << uncertainty << std::endl; + // } + // std::cout << " Total charge in window: " << total_charge << std::endl; + // } else { + // std::cout << " Warning: No grouping found in track_fitter" << std::endl; + // } + + (void)apa; + (void)face; + (void)plane; + (void)grouping; + + return corr_factor; +} From e61de5945b8d42c8396ffde18686d6e95535e27a Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Wed, 7 Jan 2026 13:33:30 -0800 Subject: [PATCH 100/111] catch up --- clus/src/Facade_Grouping.cxx | 4 ++-- clus/src/improvecluster_1.cxx | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/clus/src/Facade_Grouping.cxx b/clus/src/Facade_Grouping.cxx index c9755df7..f97abe73 100644 --- a/clus/src/Facade_Grouping.cxx +++ b/clus/src/Facade_Grouping.cxx @@ -471,7 +471,7 @@ std::tuple Grouping::convert_3Dpoint_time_ch(const geo_point_t& point, return {tind, wind}; } -std::pair Grouping::convert_time_ch_2Dpoint(const int timeslice, const int channel, const int apa, const int face, const int plane) const +std::pair Grouping::convert_time_ch_2Dpoint(const int timeslice, const int wire, const int apa, const int face, const int plane) const { if (m_anodes.size() == 0) { raise("Anode is null"); @@ -497,7 +497,7 @@ std::pair Grouping::convert_time_ch_2Dpoint(const int timeslice, if (plane >= 0 && plane < nplanes) { const double pitch = pitch_mags.at(apa).at(face).at(plane); const double center = proj_centers.at(apa).at(face).at(plane); - y = pitch * (channel+0.5) + center; + y = pitch * (wire+0.5) + center; } else { raise("invalid plane index %d", plane); diff --git a/clus/src/improvecluster_1.cxx b/clus/src/improvecluster_1.cxx index 8cc345f6..c56349d9 100644 --- a/clus/src/improvecluster_1.cxx +++ b/clus/src/improvecluster_1.cxx @@ -362,7 +362,7 @@ void ImproveCluster_1::get_activity_improved(const Cluster& cluster, std::mapconvert_time_ch_2Dpoint(time_slice, ch, apa, face, 0); + auto [x_pos, y_pos] = grouping->convert_time_ch_2Dpoint(time_slice, ch, apa, face, 0); // this should be wire index ... , so a mismatch between MicroBooNE and other detectors, need to be fixed at some points ... std::vector query_point = {static_cast(x_pos), static_cast(y_pos)}; const auto& skd = cluster.kd2d(apa, face, 0); auto ret_matches = skd.knn(1, query_point); @@ -374,7 +374,7 @@ void ImproveCluster_1::get_activity_improved(const Cluster& cluster, std::mapconvert_time_ch_2Dpoint(time_slice, ch, apa, face, 1); + auto [x_pos, y_pos] = grouping->convert_time_ch_2Dpoint(time_slice, ch, apa, face, 1); // this should be wire index ... std::vector query_point = {static_cast(x_pos), static_cast(y_pos)}; const auto& skd = cluster.kd2d(apa, face, 1); auto ret_matches = skd.knn(1, query_point); @@ -385,7 +385,7 @@ void ImproveCluster_1::get_activity_improved(const Cluster& cluster, std::mapconvert_time_ch_2Dpoint(time_slice, ch, apa, face, 2); + auto [x_pos, y_pos] = grouping->convert_time_ch_2Dpoint(time_slice, ch, apa, face, 2); // this should be wire index ... std::vector query_point = {static_cast(x_pos), static_cast(y_pos)}; const auto& skd = cluster.kd2d(apa, face, 2); auto ret_matches = skd.knn(1, query_point); @@ -403,7 +403,7 @@ void ImproveCluster_1::get_activity_improved(const Cluster& cluster, std::mapconvert_time_ch_2Dpoint(time_slice, ch, apa, face, 0); + auto [x_pos, y_pos] = grouping->convert_time_ch_2Dpoint(time_slice, ch, apa, face, 0); // this should be wire index ... std::vector query_point = {static_cast(x_pos), static_cast(y_pos)}; const auto& skd = cluster.kd2d(apa, face, 0); auto ret_matches = skd.knn(1, query_point); @@ -415,7 +415,7 @@ void ImproveCluster_1::get_activity_improved(const Cluster& cluster, std::mapconvert_time_ch_2Dpoint(time_slice, ch, apa, face, 1); + auto [x_pos, y_pos] = grouping->convert_time_ch_2Dpoint(time_slice, ch, apa, face, 1); // this should be wire index ... std::vector query_point = {static_cast(x_pos), static_cast(y_pos)}; const auto& skd = cluster.kd2d(apa, face, 1); auto ret_matches = skd.knn(1, query_point); @@ -427,7 +427,7 @@ void ImproveCluster_1::get_activity_improved(const Cluster& cluster, std::mapconvert_time_ch_2Dpoint(time_slice, ch, apa, face, 2); + auto [x_pos, y_pos] = grouping->convert_time_ch_2Dpoint(time_slice, ch, apa, face, 2); // this should be wire index ... std::vector query_point = {static_cast(x_pos), static_cast(y_pos)}; const auto& skd = cluster.kd2d(apa, face, 2); auto ret_matches = skd.knn(1, query_point); From 85d4498eeb6c1dd814713363715d7fef8caa694b Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Wed, 7 Jan 2026 17:48:37 -0800 Subject: [PATCH 101/111] update code --- clus/inc/WireCellClus/TrackFitting.h | 2 +- clus/src/TrackFitting.cxx | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/clus/inc/WireCellClus/TrackFitting.h b/clus/inc/WireCellClus/TrackFitting.h index 5c1ffa49..2962602b 100644 --- a/clus/inc/WireCellClus/TrackFitting.h +++ b/clus/inc/WireCellClus/TrackFitting.h @@ -151,7 +151,7 @@ namespace WireCell::Clus { std::shared_ptr get_graph() const { return m_graph; } void clear_graph(); - + void add_cluster(std::shared_ptr cluster); // collect charge void prepare_data(); diff --git a/clus/src/TrackFitting.cxx b/clus/src/TrackFitting.cxx index 0717c774..8fdd0422 100644 --- a/clus/src/TrackFitting.cxx +++ b/clus/src/TrackFitting.cxx @@ -264,6 +264,12 @@ void TrackFitting::add_graph(std::shared_ptr graph){ std::cout << "TrackFitting: Added graph with " << segments_set.size() << " segments." << " " << m_clusters.size() << " " << m_blobs.size() << std::endl; } +void TrackFitting::add_cluster(std::shared_ptr cluster){ + m_clusters.insert(cluster.get()); + for (auto& blob: cluster->children()){ + m_blobs.insert(blob); + } +} void TrackFitting::add_segment(std::shared_ptr segment){ m_segments.insert(segment); From f820e20afc4454fb0fdca96d6f634d65425fa64e Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Fri, 9 Jan 2026 10:51:36 -0800 Subject: [PATCH 102/111] implement the collect_2d_charge --- clus/inc/WireCellClus/TrackFitting.h | 2 + clus/src/TrackFitting.cxx | 58 ++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/clus/inc/WireCellClus/TrackFitting.h b/clus/inc/WireCellClus/TrackFitting.h index 2962602b..89a409a1 100644 --- a/clus/inc/WireCellClus/TrackFitting.h +++ b/clus/inc/WireCellClus/TrackFitting.h @@ -382,6 +382,8 @@ namespace WireCell::Clus { */ std::vector> get_wires_for_channel(int apa, int channel_number) const; + // map_apa_ch_plane_wires: (apa,channel) -> vector of (face, plane, wire) + void collect_2D_charge(std::map& charge_2d_u, std::map& charge_2d_v, std::map& charge_2d_w, std::map, std::vector>>& map_apa_ch_plane_wires); /** * Clear all caches (useful for memory management) */ diff --git a/clus/src/TrackFitting.cxx b/clus/src/TrackFitting.cxx index 8fdd0422..39119f61 100644 --- a/clus/src/TrackFitting.cxx +++ b/clus/src/TrackFitting.cxx @@ -740,6 +740,64 @@ void TrackFitting::prepare_data() { // } } +void TrackFitting::collect_2D_charge(std::map& charge_2d_u, std::map& charge_2d_v, std::map& charge_2d_w, std::map, std::vector>>& map_apa_ch_plane_wires){ + + // Clear output maps + charge_2d_u.clear(); + charge_2d_v.clear(); + charge_2d_w.clear(); + map_apa_ch_plane_wires.clear(); + + // Track which (apa, channel) pairs we've already processed for geometry map + // This avoids redundant lookups since geometry doesn't depend on time + std::set> processed_apa_channel; + + // Step 1: Iterate through m_charge_data once to: + // a) Divide charges into U/V/W maps based on plane + // b) Collect unique (apa, channel) pairs for geometry processing + for (const auto& [coord_key, charge_measurement] : m_charge_data) { + int apa = coord_key.apa; + int channel = coord_key.channel; + + // Mark this (apa, channel) for geometry processing + auto apa_ch_pair = std::make_pair(apa, channel); + processed_apa_channel.insert(apa_ch_pair); + + // Get wire information for this channel to determine plane + auto wire_info = get_wires_for_channel(apa, channel); + + // Classify charge data by plane and store in appropriate map + // A channel can map to multiple wires (wrapped wires), but they should be on same plane + for (const auto& [face, plane, wire] : wire_info) { + // Store in appropriate plane map based on plane index + // Assuming plane: 0=U, 1=V, 2=W + if (plane == 0) { + charge_2d_u[coord_key] = charge_measurement; + } else if (plane == 1) { + charge_2d_v[coord_key] = charge_measurement; + } else if (plane == 2) { + charge_2d_w[coord_key] = charge_measurement; + } + + // Only need to categorize once per channel + break; + } + } + + // Step 2: Build geometry map efficiently - only process unique (apa, channel) pairs + // This is time-independent, so we only need to do this once per channel + for (const auto& apa_ch_pair : processed_apa_channel) { + int apa = apa_ch_pair.first; + int channel = apa_ch_pair.second; + + // Get all wires for this channel (handles wrapped wires) + auto wire_info = get_wires_for_channel(apa, channel); + + // Store in map: (apa, channel) -> vector of (face, plane, wire) + map_apa_ch_plane_wires[apa_ch_pair] = wire_info; + } +} + void TrackFitting::fill_global_rb_map() { // Clear the global readout map first if (global_rb_map.size() != 0 ) return; From aa2635616a89fb899cd478393c47f6449138c2d7 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sat, 10 Jan 2026 13:59:18 -0800 Subject: [PATCH 103/111] implement the cal_kine_Charge(shower function --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 2 + clus/src/NeutrinoEnergyReco.cxx | 282 ++++++++++++++++++++ 2 files changed, 284 insertions(+) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 38c2f332..9b89ac9c 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -151,5 +151,7 @@ namespace WireCell::Clus::PR { // energy calculation ... double cal_corr_factor(WireCell::Point& pt, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); + + double cal_kine_charge(ShowerPtr Shower, Graph& graph, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); }; } diff --git a/clus/src/NeutrinoEnergyReco.cxx b/clus/src/NeutrinoEnergyReco.cxx index 270db6a6..fe2e6d14 100644 --- a/clus/src/NeutrinoEnergyReco.cxx +++ b/clus/src/NeutrinoEnergyReco.cxx @@ -92,3 +92,285 @@ double PatternAlgorithms::cal_corr_factor(WireCell::Point& pt, TrackFitting& tra return corr_factor; } + + +double PatternAlgorithms::cal_kine_charge(ShowerPtr shower, Graph& graph, TrackFitting& track_fitter, IDetectorVolumes::pointer dv){ + (void)graph; // Unused for now + + if (!shower) return 0.0; + + auto grouping = track_fitter.grouping(); + if (!grouping) return 0.0; + + double kine_energy = 0; + + // Recombination and fudge factors based on particle type + double fudge_factor = 0.95; + double recom_factor = 0.7; + + if (shower->get_flag_shower()) { + recom_factor = 0.5; // assume shower + fudge_factor = 0.8; // shower ... + } else if (std::abs(shower->get_particle_type()) == 2212) { + recom_factor = 0.35; // proton + } + + // Collect 2D charge data divided by plane + std::map charge_2d_u; + std::map charge_2d_v; + std::map charge_2d_w; + std::map, std::vector>> map_apa_ch_plane_wires; + + track_fitter.collect_2D_charge(charge_2d_u, charge_2d_v, charge_2d_w, map_apa_ch_plane_wires); + + // Get point clouds from shower + auto pcloud1 = shower->get_pcloud("associate_points"); // associated points + auto pcloud2 = shower->get_pcloud("fit"); // fit points + + if (!pcloud1 && !pcloud2) return 0; + if (!pcloud1 && pcloud2) pcloud1 = pcloud2; + if (!pcloud2 && pcloud1) pcloud2 = pcloud1; + + double sum_u_charge = 0; + double sum_v_charge = 0; + double sum_w_charge = 0; + + double dis_cut = 0.6 * units::cm; + + // Process U plane charges + for (const auto& [coord_key, charge_data] : charge_2d_u) { + int time_slice = coord_key.time; + int channel = coord_key.channel; + int apa = coord_key.apa; + + // Get wire info for this channel + auto apa_ch_key = std::make_pair(apa, channel); + auto wire_it = map_apa_ch_plane_wires.find(apa_ch_key); + if (wire_it == map_apa_ch_plane_wires.end()) continue; + + int face = -1; + for (const auto& [f, plane, wire] : wire_it->second) { + if (plane == 0) { // U plane + face = f; + break; + } + } + if (face < 0) continue; + + // Convert time and channel to 2D point + auto p2d = grouping->convert_time_ch_2Dpoint(time_slice, channel, apa, face, 0); + WireCell::Point test_p2d(p2d.first, p2d.second, 0); + + // Find closest 3D point in point clouds + double dis = 1e9; + size_t point_index = 0; + const Facade::Cluster* closest_cluster = nullptr; + + if (pcloud1) { + auto result = pcloud1->get_closest_2d_point_info(test_p2d, 0, face, apa); + dis = std::get<0>(result); + closest_cluster = std::get<1>(result); + point_index = std::get<2>(result); + } + + if (dis < dis_cut && closest_cluster) { + const auto& points = pcloud1->get_points(); + if (point_index < points.size()) { + WireCell::Point test_p(points[point_index].x, points[point_index].y, points[point_index].z); + double factor = cal_corr_factor(test_p, track_fitter, dv); + sum_u_charge += charge_data.charge * factor; + } + } else if (pcloud2) { + // Try second point cloud + auto result = pcloud2->get_closest_2d_point_info(test_p2d, 0, face, apa); + dis = std::get<0>(result); + closest_cluster = std::get<1>(result); + point_index = std::get<2>(result); + + if (dis < dis_cut && closest_cluster) { + const auto& points = pcloud2->get_points(); + if (point_index < points.size()) { + WireCell::Point test_p(points[point_index].x, points[point_index].y, points[point_index].z); + double factor = cal_corr_factor(test_p, track_fitter, dv); + sum_u_charge += charge_data.charge * factor; + } + } + } + } + + // Process V plane charges + for (const auto& [coord_key, charge_data] : charge_2d_v) { + int time_slice = coord_key.time; + int channel = coord_key.channel; + int apa = coord_key.apa; + + // Get wire info for this channel + auto apa_ch_key = std::make_pair(apa, channel); + auto wire_it = map_apa_ch_plane_wires.find(apa_ch_key); + if (wire_it == map_apa_ch_plane_wires.end()) continue; + + int face = -1; + for (const auto& [f, plane, wire] : wire_it->second) { + if (plane == 1) { // V plane + face = f; + break; + } + } + if (face < 0) continue; + + // Convert time and channel to 2D point + auto p2d = grouping->convert_time_ch_2Dpoint(time_slice, channel, apa, face, 1); + WireCell::Point test_p2d(p2d.first, p2d.second, 0); + + // Find closest 3D point in point clouds + double dis = 1e9; + size_t point_index = 0; + const Facade::Cluster* closest_cluster = nullptr; + + if (pcloud1) { + auto result = pcloud1->get_closest_2d_point_info(test_p2d, 1, face, apa); + dis = std::get<0>(result); + closest_cluster = std::get<1>(result); + point_index = std::get<2>(result); + } + + if (dis < dis_cut && closest_cluster) { + const auto& points = pcloud1->get_points(); + if (point_index < points.size()) { + WireCell::Point test_p(points[point_index].x, points[point_index].y, points[point_index].z); + double factor = cal_corr_factor(test_p, track_fitter, dv); + sum_v_charge += charge_data.charge * factor; + } + } else if (pcloud2) { + // Try second point cloud + auto result = pcloud2->get_closest_2d_point_info(test_p2d, 1, face, apa); + dis = std::get<0>(result); + closest_cluster = std::get<1>(result); + point_index = std::get<2>(result); + + if (dis < dis_cut && closest_cluster) { + const auto& points = pcloud2->get_points(); + if (point_index < points.size()) { + WireCell::Point test_p(points[point_index].x, points[point_index].y, points[point_index].z); + double factor = cal_corr_factor(test_p, track_fitter, dv); + sum_v_charge += charge_data.charge * factor; + } + } + } + } + + // Process W plane charges + for (const auto& [coord_key, charge_data] : charge_2d_w) { + int time_slice = coord_key.time; + int channel = coord_key.channel; + int apa = coord_key.apa; + + // Get wire info for this channel + auto apa_ch_key = std::make_pair(apa, channel); + auto wire_it = map_apa_ch_plane_wires.find(apa_ch_key); + if (wire_it == map_apa_ch_plane_wires.end()) continue; + + int face = -1; + for (const auto& [f, plane, wire] : wire_it->second) { + if (plane == 2) { // W plane + face = f; + break; + } + } + if (face < 0) continue; + + // Convert time and channel to 2D point + auto p2d = grouping->convert_time_ch_2Dpoint(time_slice, channel, apa, face, 2); + WireCell::Point test_p2d(p2d.first, p2d.second, 0); + + // Find closest 3D point in point clouds + double dis = 1e9; + size_t point_index = 0; + const Facade::Cluster* closest_cluster = nullptr; + + if (pcloud1) { + auto result = pcloud1->get_closest_2d_point_info(test_p2d, 2, face, apa); + dis = std::get<0>(result); + closest_cluster = std::get<1>(result); + point_index = std::get<2>(result); + } + + if (dis < dis_cut && closest_cluster) { + const auto& points = pcloud1->get_points(); + if (point_index < points.size()) { + WireCell::Point test_p(points[point_index].x, points[point_index].y, points[point_index].z); + double factor = cal_corr_factor(test_p, track_fitter, dv); + sum_w_charge += charge_data.charge * factor; + } + } else if (pcloud2) { + // Try second point cloud + auto result = pcloud2->get_closest_2d_point_info(test_p2d, 2, face, apa); + dis = std::get<0>(result); + closest_cluster = std::get<1>(result); + point_index = std::get<2>(result); + + if (dis < dis_cut && closest_cluster) { + const auto& points = pcloud2->get_points(); + if (point_index < points.size()) { + WireCell::Point test_p(points[point_index].x, points[point_index].y, points[point_index].z); + double factor = cal_corr_factor(test_p, track_fitter, dv); + sum_w_charge += charge_data.charge * factor; + } + } + } + } + + // Calculate overall charge using weighted average + double charge[3] = {sum_u_charge, sum_v_charge, sum_w_charge}; + double weight[3] = {0.25, 0.25, 1.0}; + + // Find min, max, and median charges + int min_index = 0, max_index = 0, med_index = 0; + double min_charge = 1e9, max_charge = -1e9; + for (int i = 0; i < 3; i++) { + if (min_charge > charge[i]) { + min_charge = charge[i]; + min_index = i; + } + if (max_charge < charge[i]) { + max_charge = charge[i]; + max_index = i; + } + } + + if (min_index != max_index) { + for (int i = 0; i < 3; i++) { + if (i == min_index) continue; + if (i == max_index) continue; + med_index = i; + } + } else { + min_index = 0; + med_index = 1; + max_index = 2; + } + + // Calculate asymmetries + double max_asy = 0; + if (charge[med_index] + charge[max_index] > 0) { + max_asy = std::abs(charge[med_index] - charge[max_index]) / + (charge[med_index] + charge[max_index]); + } + + // Calculate overall charge + double overall_charge = (weight[0]*charge[0] + weight[1]*charge[1] + weight[2]*charge[2]) / + (weight[0] + weight[1] + weight[2]); + + // Exclude maximal charge if asymmetry is too large + if (max_asy > 0.04) { + overall_charge = (weight[med_index] * charge[med_index] + + weight[min_index] * charge[min_index]) / + (weight[med_index] + weight[min_index]); + } + + // Convert charge to kinetic energy + // Using W-value of 23.6 eV per electron-ion pair + kine_energy = overall_charge / recom_factor / fudge_factor * 23.6 / 1e6 * units::MeV; + + return kine_energy; +} \ No newline at end of file From 89f6b6869934d1508684a0e0706f77885fc490d6 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sat, 10 Jan 2026 14:02:56 -0800 Subject: [PATCH 104/111] implement cal_kine_charge function --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 2 +- clus/src/NeutrinoEnergyReco.cxx | 282 ++++++++++++++++++++ 2 files changed, 283 insertions(+), 1 deletion(-) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 9b89ac9c..72e4f0ff 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -151,7 +151,7 @@ namespace WireCell::Clus::PR { // energy calculation ... double cal_corr_factor(WireCell::Point& pt, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); - double cal_kine_charge(ShowerPtr Shower, Graph& graph, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); + double cal_kine_charge(SegmentPtr segment, Graph& graph, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); }; } diff --git a/clus/src/NeutrinoEnergyReco.cxx b/clus/src/NeutrinoEnergyReco.cxx index fe2e6d14..5eca4d5a 100644 --- a/clus/src/NeutrinoEnergyReco.cxx +++ b/clus/src/NeutrinoEnergyReco.cxx @@ -372,5 +372,287 @@ double PatternAlgorithms::cal_kine_charge(ShowerPtr shower, Graph& graph, TrackF // Using W-value of 23.6 eV per electron-ion pair kine_energy = overall_charge / recom_factor / fudge_factor * 23.6 / 1e6 * units::MeV; + return kine_energy; +} + +double PatternAlgorithms::cal_kine_charge(SegmentPtr segment, Graph& graph, TrackFitting& track_fitter, IDetectorVolumes::pointer dv){ + (void)graph; // Unused for now + + if (!segment) return 0.0; + + auto grouping = track_fitter.grouping(); + if (!grouping) return 0.0; + + double kine_energy = 0; + + // Recombination and fudge factors based on particle type + double fudge_factor = 0.95; + double recom_factor = 0.7; + + // Check if segment has shower topology or trajectory flags + if (segment->flags_any(PR::SegmentFlags::kShowerTopology)) { + recom_factor = 0.5; // assume shower + fudge_factor = 0.8; // shower ... + } else if (segment->has_particle_info() && std::abs(segment->particle_info()->pdg()) == 2212) { + recom_factor = 0.35; // proton + } + + // Collect 2D charge data divided by plane + std::map charge_2d_u; + std::map charge_2d_v; + std::map charge_2d_w; + std::map, std::vector>> map_apa_ch_plane_wires; + + track_fitter.collect_2D_charge(charge_2d_u, charge_2d_v, charge_2d_w, map_apa_ch_plane_wires); + + // Get point clouds from segment + auto pcloud1 = segment->dpcloud("associate_points"); // associated points + auto pcloud2 = segment->dpcloud("fit"); // fit points + + if (!pcloud1 && !pcloud2) return 0; + if (!pcloud1 && pcloud2) pcloud1 = pcloud2; + if (!pcloud2 && pcloud1) pcloud2 = pcloud1; + + double sum_u_charge = 0; + double sum_v_charge = 0; + double sum_w_charge = 0; + + double dis_cut = 0.6 * units::cm; + + // Process U plane charges + for (const auto& [coord_key, charge_data] : charge_2d_u) { + int time_slice = coord_key.time; + int channel = coord_key.channel; + int apa = coord_key.apa; + + // Get wire info for this channel + auto apa_ch_key = std::make_pair(apa, channel); + auto wire_it = map_apa_ch_plane_wires.find(apa_ch_key); + if (wire_it == map_apa_ch_plane_wires.end()) continue; + + int face = -1; + for (const auto& [f, plane, wire] : wire_it->second) { + if (plane == 0) { // U plane + face = f; + break; + } + } + if (face < 0) continue; + + // Convert time and channel to 2D point + auto p2d = grouping->convert_time_ch_2Dpoint(time_slice, channel, apa, face, 0); + WireCell::Point test_p2d(p2d.first, p2d.second, 0); + + // Find closest 3D point in point clouds + double dis = 1e9; + size_t point_index = 0; + const Facade::Cluster* closest_cluster = nullptr; + + if (pcloud1) { + auto result = pcloud1->get_closest_2d_point_info(test_p2d, 0, face, apa); + dis = std::get<0>(result); + closest_cluster = std::get<1>(result); + point_index = std::get<2>(result); + } + + if (dis < dis_cut && closest_cluster) { + const auto& points = pcloud1->get_points(); + if (point_index < points.size()) { + WireCell::Point test_p(points[point_index].x, points[point_index].y, points[point_index].z); + double factor = cal_corr_factor(test_p, track_fitter, dv); + sum_u_charge += charge_data.charge * factor; + } + } else if (pcloud2) { + // Try second point cloud + auto result = pcloud2->get_closest_2d_point_info(test_p2d, 0, face, apa); + dis = std::get<0>(result); + closest_cluster = std::get<1>(result); + point_index = std::get<2>(result); + + if (dis < dis_cut && closest_cluster) { + const auto& points = pcloud2->get_points(); + if (point_index < points.size()) { + WireCell::Point test_p(points[point_index].x, points[point_index].y, points[point_index].z); + double factor = cal_corr_factor(test_p, track_fitter, dv); + sum_u_charge += charge_data.charge * factor; + } + } + } + } + + // Process V plane charges + for (const auto& [coord_key, charge_data] : charge_2d_v) { + int time_slice = coord_key.time; + int channel = coord_key.channel; + int apa = coord_key.apa; + + // Get wire info for this channel + auto apa_ch_key = std::make_pair(apa, channel); + auto wire_it = map_apa_ch_plane_wires.find(apa_ch_key); + if (wire_it == map_apa_ch_plane_wires.end()) continue; + + int face = -1; + for (const auto& [f, plane, wire] : wire_it->second) { + if (plane == 1) { // V plane + face = f; + break; + } + } + if (face < 0) continue; + + // Convert time and channel to 2D point + auto p2d = grouping->convert_time_ch_2Dpoint(time_slice, channel, apa, face, 1); + WireCell::Point test_p2d(p2d.first, p2d.second, 0); + + // Find closest 3D point in point clouds + double dis = 1e9; + size_t point_index = 0; + const Facade::Cluster* closest_cluster = nullptr; + + if (pcloud1) { + auto result = pcloud1->get_closest_2d_point_info(test_p2d, 1, face, apa); + dis = std::get<0>(result); + closest_cluster = std::get<1>(result); + point_index = std::get<2>(result); + } + + if (dis < dis_cut && closest_cluster) { + const auto& points = pcloud1->get_points(); + if (point_index < points.size()) { + WireCell::Point test_p(points[point_index].x, points[point_index].y, points[point_index].z); + double factor = cal_corr_factor(test_p, track_fitter, dv); + sum_v_charge += charge_data.charge * factor; + } + } else if (pcloud2) { + // Try second point cloud + auto result = pcloud2->get_closest_2d_point_info(test_p2d, 1, face, apa); + dis = std::get<0>(result); + closest_cluster = std::get<1>(result); + point_index = std::get<2>(result); + + if (dis < dis_cut && closest_cluster) { + const auto& points = pcloud2->get_points(); + if (point_index < points.size()) { + WireCell::Point test_p(points[point_index].x, points[point_index].y, points[point_index].z); + double factor = cal_corr_factor(test_p, track_fitter, dv); + sum_v_charge += charge_data.charge * factor; + } + } + } + } + + // Process W plane charges + for (const auto& [coord_key, charge_data] : charge_2d_w) { + int time_slice = coord_key.time; + int channel = coord_key.channel; + int apa = coord_key.apa; + + // Get wire info for this channel + auto apa_ch_key = std::make_pair(apa, channel); + auto wire_it = map_apa_ch_plane_wires.find(apa_ch_key); + if (wire_it == map_apa_ch_plane_wires.end()) continue; + + int face = -1; + for (const auto& [f, plane, wire] : wire_it->second) { + if (plane == 2) { // W plane + face = f; + break; + } + } + if (face < 0) continue; + + // Convert time and channel to 2D point + auto p2d = grouping->convert_time_ch_2Dpoint(time_slice, channel, apa, face, 2); + WireCell::Point test_p2d(p2d.first, p2d.second, 0); + + // Find closest 3D point in point clouds + double dis = 1e9; + size_t point_index = 0; + const Facade::Cluster* closest_cluster = nullptr; + + if (pcloud1) { + auto result = pcloud1->get_closest_2d_point_info(test_p2d, 2, face, apa); + dis = std::get<0>(result); + closest_cluster = std::get<1>(result); + point_index = std::get<2>(result); + } + + if (dis < dis_cut && closest_cluster) { + const auto& points = pcloud1->get_points(); + if (point_index < points.size()) { + WireCell::Point test_p(points[point_index].x, points[point_index].y, points[point_index].z); + double factor = cal_corr_factor(test_p, track_fitter, dv); + sum_w_charge += charge_data.charge * factor; + } + } else if (pcloud2) { + // Try second point cloud + auto result = pcloud2->get_closest_2d_point_info(test_p2d, 2, face, apa); + dis = std::get<0>(result); + closest_cluster = std::get<1>(result); + point_index = std::get<2>(result); + + if (dis < dis_cut && closest_cluster) { + const auto& points = pcloud2->get_points(); + if (point_index < points.size()) { + WireCell::Point test_p(points[point_index].x, points[point_index].y, points[point_index].z); + double factor = cal_corr_factor(test_p, track_fitter, dv); + sum_w_charge += charge_data.charge * factor; + } + } + } + } + + // Calculate overall charge using weighted average + double charge[3] = {sum_u_charge, sum_v_charge, sum_w_charge}; + double weight[3] = {0.25, 0.25, 1.0}; + + // Find min, max, and median charges + int min_index = 0, max_index = 0, med_index = 0; + double min_charge = 1e9, max_charge = -1e9; + for (int i = 0; i < 3; i++) { + if (min_charge > charge[i]) { + min_charge = charge[i]; + min_index = i; + } + if (max_charge < charge[i]) { + max_charge = charge[i]; + max_index = i; + } + } + + if (min_index != max_index) { + for (int i = 0; i < 3; i++) { + if (i == min_index) continue; + if (i == max_index) continue; + med_index = i; + } + } else { + min_index = 0; + med_index = 1; + max_index = 2; + } + + // Calculate asymmetries + double max_asy = 0; + if (charge[med_index] + charge[max_index] > 0) { + max_asy = std::abs(charge[med_index] - charge[max_index]) / + (charge[med_index] + charge[max_index]); + } + + // Calculate overall charge + double overall_charge = (weight[0]*charge[0] + weight[1]*charge[1] + weight[2]*charge[2]) / + (weight[0] + weight[1] + weight[2]); + + // Exclude maximal charge if asymmetry is too large + if (max_asy > 0.04) { + overall_charge = (weight[med_index] * charge[med_index] + + weight[min_index] * charge[min_index]) / + (weight[med_index] + weight[min_index]); + } + + // Convert charge to kinetic energy + // Using W-value of 23.6 eV per electron-ion pair + kine_energy = overall_charge / recom_factor / fudge_factor * 23.6 / 1e6 * units::MeV; + return kine_energy; } \ No newline at end of file From 0a5fa7e45a7c9efca4e81bff56c9f49ca5ae5d14 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sat, 10 Jan 2026 14:10:04 -0800 Subject: [PATCH 105/111] add code --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 3 ++- clus/src/NeutrinoEnergyReco.cxx | 29 +++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 72e4f0ff..8e130ff7 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -139,7 +139,7 @@ namespace WireCell::Clus::PR { void shower_clustering_connecting_to_main_vertex(Graph& graph, VertexPtr main_vertex, std::set& showers, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters); void shower_clustering_with_nv_from_main_cluster(Graph& graph, VertexPtr main_vertex, Facade::Cluster* main_cluster, std::set& showers, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters); void shower_clustering_with_nv_from_vertices(Graph& graph, VertexPtr main_vertex, Facade::Cluster* main_cluster, std::vector& other_clusters, std::set& showers, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); - + void calculate_shower_kinematics(std::set& showers, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, Graph& graph, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); // deghost related functions ... @@ -153,5 +153,6 @@ namespace WireCell::Clus::PR { double cal_corr_factor(WireCell::Point& pt, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); double cal_kine_charge(ShowerPtr Shower, Graph& graph, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); double cal_kine_charge(SegmentPtr segment, Graph& graph, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); + }; } diff --git a/clus/src/NeutrinoEnergyReco.cxx b/clus/src/NeutrinoEnergyReco.cxx index 5eca4d5a..447e3e44 100644 --- a/clus/src/NeutrinoEnergyReco.cxx +++ b/clus/src/NeutrinoEnergyReco.cxx @@ -655,4 +655,33 @@ double PatternAlgorithms::cal_kine_charge(SegmentPtr segment, Graph& graph, Trac kine_energy = overall_charge / recom_factor / fudge_factor * 23.6 / 1e6 * units::MeV; return kine_energy; +} + +void PatternAlgorithms::calculate_shower_kinematics(std::set& showers, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, Graph& graph, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model){ + (void)vertices_in_long_muon; // Currently unused + + for (auto& shower : showers) { + if (!shower) continue; + + // Skip if kinematics already calculated + if (shower->get_flag_kinematics()) continue; + + // Get particle type (PDG code) + int particle_type = shower->get_particle_type(); + + // Check if it's a muon (PDG code = ±13) + if (std::abs(particle_type) != 13) { + // Not a muon - use regular kinematics calculation + shower->calculate_kinematics(particle_data, recomb_model); + double kine_charge = cal_kine_charge(shower, graph, track_fitter, dv); + shower->set_kine_charge(kine_charge); + shower->set_flag_kinematics(true); + } else { + // Long muon - use special kinematics calculation + shower->calculate_kinematics_long_muon(segments_in_long_muon, particle_data, recomb_model); + double kine_charge = cal_kine_charge(shower, graph, track_fitter, dv); + shower->set_kine_charge(kine_charge); + shower->set_flag_kinematics(true); + } + } } \ No newline at end of file From 95a804ac236cbdff38d8db5fd3894d666b271a37 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sat, 10 Jan 2026 14:22:16 -0800 Subject: [PATCH 106/111] implement the examine_merged_showers --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 1 + clus/src/NeutrinoShowerClustering.cxx | 84 +++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 8e130ff7..a3dfec35 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -140,6 +140,7 @@ namespace WireCell::Clus::PR { void shower_clustering_with_nv_from_main_cluster(Graph& graph, VertexPtr main_vertex, Facade::Cluster* main_cluster, std::set& showers, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters); void shower_clustering_with_nv_from_vertices(Graph& graph, VertexPtr main_vertex, Facade::Cluster* main_cluster, std::vector& other_clusters, std::set& showers, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); void calculate_shower_kinematics(std::set& showers, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, Graph& graph, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); + void examine_merge_showers(std::set& showers, VertexPtr main_vertex, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, Graph& graph, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); // deghost related functions ... diff --git a/clus/src/NeutrinoShowerClustering.cxx b/clus/src/NeutrinoShowerClustering.cxx index c73f2e3e..d3f8d86a 100644 --- a/clus/src/NeutrinoShowerClustering.cxx +++ b/clus/src/NeutrinoShowerClustering.cxx @@ -1197,4 +1197,88 @@ void PatternAlgorithms::shower_clustering_with_nv_from_vertices(Graph& graph, Ve } std::cout << "With separated-cluster shower: " << showers.size() << std::endl; +} + +void PatternAlgorithms::examine_merge_showers(std::set& showers, VertexPtr main_vertex, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, Graph& graph, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model){ + + if (!main_vertex || map_vertex_to_shower.find(main_vertex) == map_vertex_to_shower.end()) { + return; + } + + // Get all showers starting at main vertex + auto& main_vertex_showers = map_vertex_to_shower[main_vertex]; + + // Track which showers have been merged + std::set showers_to_remove; + bool flag_update = false; + + // Iterate through all showers from main vertex + for (auto shower1 : main_vertex_showers) { + // Skip if already processed + if (showers_to_remove.find(shower1) != showers_to_remove.end()) continue; + + // Skip muons (particle type 13) + if (shower1->get_particle_type() == 13) continue; + + // Check start vertex type + auto [start_vtx1, start_type1] = shower1->get_start_vertex_and_type(); + if (start_type1 != 1) continue; + + // Calculate direction for shower1 (100 cm distance cut) + WireCell::Point start_point1 = shower1->get_start_point(); + WireCell::Vector dir1 = shower_cal_dir_3vector(*shower1, start_point1, 100 * units::cm); + + // Look for candidate showers to merge + for (auto shower2 : main_vertex_showers) { + // Skip if same shower + if (shower1 == shower2) continue; + + // Skip if already processed + if (showers_to_remove.find(shower2) != showers_to_remove.end()) continue; + + // Skip muons + if (shower2->get_particle_type() == 13) continue; + + // Check start vertex type + auto [start_vtx2, start_type2] = shower2->get_start_vertex_and_type(); + if (start_type2 != 2) continue; + + // Calculate direction for shower2 + WireCell::Point start_point2 = shower2->get_start_point(); + WireCell::Vector dir2 = shower_cal_dir_3vector(*shower2, start_point2, 100 * units::cm); + + // Calculate angle between directions + double cos_angle = dir1.dot(dir2) / (dir1.magnitude() * dir2.magnitude()); + double angle = std::acos(std::max(-1.0, std::min(1.0, cos_angle))); + double angle_deg = angle * 180.0 / M_PI; + + // Merge if angle is less than 10 degrees + if (angle_deg < 10.0) { + // Merge shower2 into shower1 + shower1->add_shower(*shower2); + + // Update particle type and kinematics + shower1->update_particle_type(particle_data, recomb_model); + shower1->calculate_kinematics(particle_data, recomb_model); + + // Recalculate kinetic charge + cal_kine_charge(shower1, graph, track_fitter, dv); + + // Mark shower2 for removal + showers_to_remove.insert(shower2); + flag_update = true; + } + } + } + + // Remove merged showers from the main set + for (auto shower : showers_to_remove) { + showers.erase(shower); + } + + // Update shower maps if any merges occurred + if (flag_update) { + update_shower_maps(showers, map_vertex_in_shower, map_segment_in_shower, + map_vertex_to_shower, used_shower_clusters); + } } \ No newline at end of file From dfa78f343a750e4efeecc2864781f7367128e1bc Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sat, 10 Jan 2026 14:40:37 -0800 Subject: [PATCH 107/111] implement the shower_clustering_in_other_clusters function --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 1 + clus/src/NeutrinoShowerClustering.cxx | 285 +++++++++++++++++++- 2 files changed, 285 insertions(+), 1 deletion(-) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index a3dfec35..4110fede 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -141,6 +141,7 @@ namespace WireCell::Clus::PR { void shower_clustering_with_nv_from_vertices(Graph& graph, VertexPtr main_vertex, Facade::Cluster* main_cluster, std::vector& other_clusters, std::set& showers, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); void calculate_shower_kinematics(std::set& showers, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, Graph& graph, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); void examine_merge_showers(std::set& showers, VertexPtr main_vertex, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, Graph& graph, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); + void shower_clustering_in_other_clusters(Graph& graph, VertexPtr main_vertex, std::set& showers, Facade::Cluster* main_cluster, std::vector& other_clusters, std::map map_cluster_main_vertices, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); // deghost related functions ... diff --git a/clus/src/NeutrinoShowerClustering.cxx b/clus/src/NeutrinoShowerClustering.cxx index d3f8d86a..5a6fd510 100644 --- a/clus/src/NeutrinoShowerClustering.cxx +++ b/clus/src/NeutrinoShowerClustering.cxx @@ -1262,7 +1262,9 @@ void PatternAlgorithms::examine_merge_showers(std::set& showers, Vert shower1->calculate_kinematics(particle_data, recomb_model); // Recalculate kinetic charge - cal_kine_charge(shower1, graph, track_fitter, dv); + double kine_charge = cal_kine_charge(shower1, graph, track_fitter, dv); + shower1->set_kine_charge(kine_charge); + shower1->set_flag_kinematics(true); // Mark shower2 for removal showers_to_remove.insert(shower2); @@ -1281,4 +1283,285 @@ void PatternAlgorithms::examine_merge_showers(std::set& showers, Vert update_shower_maps(showers, map_vertex_in_shower, map_segment_in_shower, map_vertex_to_shower, used_shower_clusters); } +} + + +void PatternAlgorithms::shower_clustering_in_other_clusters(Graph& graph, VertexPtr main_vertex, std::set& showers, Facade::Cluster* main_cluster, std::vector& other_clusters, std::map map_cluster_main_vertices, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model){ + + if (!main_vertex || !main_cluster) return; + + // Build map_vertex_segments and map_segment_vertices + std::map> map_vertex_segments; + std::map> map_segment_vertices; + + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr seg = graph[*eit].segment; + if (!seg) continue; + + auto source_vdesc = boost::source(*eit, graph); + auto target_vdesc = boost::target(*eit, graph); + VertexPtr v1 = graph[source_vdesc].vertex; + VertexPtr v2 = graph[target_vdesc].vertex; + + if (v1) { + map_vertex_segments[v1].insert(seg); + map_segment_vertices[seg].insert(v1); + } + if (v2) { + map_vertex_segments[v2].insert(seg); + map_segment_vertices[seg].insert(v2); + } + } + + // Build map_cluster_length + std::map map_cluster_length; + for (auto cluster : other_clusters) { + map_cluster_length[cluster] = cluster->get_length(); + } + + // Collect vertices in main cluster as well as existing showers + std::vector vertices; + for (auto& [vtx, seg_set] : map_vertex_segments) { + if ((vtx->cluster() && vtx->cluster()->get_cluster_id() == main_cluster->get_cluster_id()) || + map_vertex_in_shower.find(vtx) != map_vertex_in_shower.end()) { + vertices.push_back(vtx); + } + } + + // Process clusters in map_cluster_main_vertices + for (auto& [cluster, vertex] : map_cluster_main_vertices) { + if (used_shower_clusters.find(cluster) != used_shower_clusters.end()) continue; + if (map_cluster_length[cluster] < 4 * units::cm) continue; + + double min_dis = 1e9; + VertexPtr min_vertex = nullptr; + double main_dis = 1e9; + + for (auto vtx : vertices) { + WireCell::Point vtx_pt = vtx->fit().valid() ? vtx->fit().point : vtx->wcpt().point; + + // Get closest distance using cluster's get_closest_dis method + double dis = cluster->get_closest_dis(vtx_pt); + + if (dis < min_dis) { + min_dis = dis; + min_vertex = vtx; + } + + if (vtx == main_vertex) { + main_dis = dis; + } + } + + if (min_dis > 0.8 * main_dis) { + min_dis = main_dis; + min_vertex = main_vertex; + } + + // Find a shower segment starting at vertex + SegmentPtr sg = nullptr; + if (map_vertex_segments.find(vertex) != map_vertex_segments.end()) { + for (auto seg : map_vertex_segments[vertex]) { + if (seg->flags_any(SegmentFlags::kShowerTrajectory) || + seg->flags_any(SegmentFlags::kShowerTopology)) { + sg = seg; + break; + } + } + } + + int connection_type = 3; + + if (sg) { + // Create new shower + ShowerPtr shower = std::make_shared(graph); + shower->set_start_vertex(min_vertex, connection_type); + shower->set_start_segment(sg); + + // Set direction based on distance to vertex + WireCell::Point vertex_pt = vertex->fit().valid() ? vertex->fit().point : vertex->wcpt().point; + const auto& fits = sg->fits(); + if (!fits.empty()) { + double dis1 = (vertex_pt - fits.front().point).magnitude(); + double dis2 = (vertex_pt - fits.back().point).magnitude(); + + if (dis1 < dis2) { + sg->dirsign(1); + } else { + sg->dirsign(-1); + } + } + + // Complete shower structure + std::set used_segments; + shower->complete_structure_with_start_segment(used_segments); + + // Calculate shower direction + WireCell::Vector dir_shower = shower_cal_dir_3vector(*shower, vertex_pt, 15 * units::cm); + + // Cluster with the rest - add segments based on angle and distance + for (auto& [seg1, vertices_set] : map_segment_vertices) { + if (seg1->cluster() == main_cluster) continue; + if (map_segment_in_shower.find(seg1) != map_segment_in_shower.end()) continue; + if (seg1->cluster() == shower->start_segment()->cluster()) continue; + + // Find the closest point + auto [pair_dis, pair_point] = segment_get_closest_point(seg1, vertex_pt); + WireCell::Vector v1(pair_point.x() - vertex_pt.x(), + pair_point.y() - vertex_pt.y(), + pair_point.z() - vertex_pt.z()); + + double angle = std::acos(std::clamp(dir_shower.dot(v1) / (dir_shower.magnitude() * v1.magnitude()), -1.0, 1.0)); + angle = angle / M_PI * 180.0; + + if ((angle < 25 && pair_dis < 80 * units::cm) || + (angle < 12.5 && pair_dis < 120 * units::cm)) { + shower->add_segment(seg1); + } + } + + // Update particle type + shower->update_particle_type(particle_data, recomb_model); + + // Check with other showers and merge if needed + std::vector showers_to_be_removed; + for (auto shower1 : showers) { + WireCell::Point start_pt1 = shower1->get_start_point(); + WireCell::Vector dir_shower1 = shower_cal_dir_3vector(*shower1, start_pt1, 15 * units::cm); + WireCell::Vector dir2(start_pt1.x() - vertex_pt.x(), + start_pt1.y() - vertex_pt.y(), + start_pt1.z() - vertex_pt.z()); + + double angle = std::acos(std::clamp(dir_shower.dot(dir_shower1) / (dir_shower.magnitude() * dir_shower1.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + double angle1 = std::acos(std::clamp(dir_shower.dot(dir2) / (dir_shower.magnitude() * dir2.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + + if ((angle < 25 && angle1 < 15 && dir2.magnitude() < 80 * units::cm) || + (angle < 12.5 && angle1 < 7.5 && dir2.magnitude() < 120 * units::cm)) { + shower->add_shower(*shower1); + showers_to_be_removed.push_back(shower1); + } + } + + for (auto shower_to_remove : showers_to_be_removed) { + showers.erase(shower_to_remove); + } + + showers.insert(shower); + } + } + + update_shower_maps(showers, map_vertex_in_shower, map_segment_in_shower, + map_vertex_to_shower, used_shower_clusters); + + // Process remaining other_clusters not in map_cluster_main_vertices + for (auto cluster : other_clusters) { + if (used_shower_clusters.find(cluster) != used_shower_clusters.end()) continue; + + double min_dis = 1e9; + VertexPtr min_vertex = nullptr; + double main_dis = 1e9; + + for (auto vtx : vertices) { + WireCell::Point vtx_pt = vtx->fit().valid() ? vtx->fit().point : vtx->wcpt().point; + + // Get closest distance using cluster's get_closest_dis method + double dis = cluster->get_closest_dis(vtx_pt); + + if (dis < min_dis) { + min_dis = dis; + min_vertex = vtx; + } + + if (vtx == main_vertex) { + main_dis = dis; + } + } + + if (min_dis > 0.8 * main_dis) { + min_dis = main_dis; + min_vertex = main_vertex; + } + + // Find a segment from this cluster + SegmentPtr sg = nullptr; + for (auto& [seg, vertices_set] : map_segment_vertices) { + if (seg->cluster() != cluster) continue; + sg = seg; + break; + } + + int connection_type = 3; + if (min_dis > 80 * units::cm) { + connection_type = 4; + } + + if (sg) { + // Create new shower + ShowerPtr shower = std::make_shared(graph); + shower->set_start_vertex(min_vertex, connection_type); + shower->set_start_segment(sg); + + // Set direction if not already set + if (sg->dirsign() == 0) { + // Get start and end vertices + auto seg_edesc = sg->get_descriptor(); + auto source_vdesc = boost::source(seg_edesc, graph); + auto target_vdesc = boost::target(seg_edesc, graph); + VertexPtr v1 = graph[source_vdesc].vertex; + VertexPtr v2 = graph[target_vdesc].vertex; + + if (v1 && v2) { + if (map_vertex_segments[v1].size() == 1 && map_vertex_segments[v2].size() > 1) { + sg->dirsign(1); + } else if (map_vertex_segments[v1].size() > 1 && map_vertex_segments[v2].size() == 1) { + sg->dirsign(-1); + } else { + // Examine vertices based on distance to main_vertex + WireCell::Point main_vtx_pt = main_vertex->fit().valid() ? main_vertex->fit().point : main_vertex->wcpt().point; + const auto& fits = sg->fits(); + if (!fits.empty()) { + double dis1 = (main_vtx_pt - fits.front().point).magnitude(); + double dis2 = (main_vtx_pt - fits.back().point).magnitude(); + + if (dis1 < dis2) { + sg->dirsign(1); + } else { + sg->dirsign(-1); + } + } + } + } + } + + // Set particle type to electron if needed + int particle_type = 0; + if (sg->has_particle_info() && sg->particle_info()) { + particle_type = sg->particle_info()->pdg(); + } + + if (particle_type == 0 || + (std::abs(particle_type) == 13 && segment_track_length(sg) < 40 * units::cm && sg->dir_weak())) { + auto four_momentum = segment_cal_4mom(sg, 11, particle_data, recomb_model); + + // Create ParticleInfo for electron + auto pinfo = std::make_shared( + 11, // electron PDG + particle_data->get_particle_mass(11), // electron mass + particle_data->pdg_to_name(11), // "electron" + four_momentum // 4-momentum + ); + + sg->particle_info(pinfo); + } + + // Complete shower structure + std::set used_segments; + shower->complete_structure_with_start_segment(used_segments); + showers.insert(shower); + } + } + + update_shower_maps(showers, map_vertex_in_shower, map_segment_in_shower, + map_vertex_to_shower, used_shower_clusters); } \ No newline at end of file From 7613ea71eea4057fec7804c58643a0d9886b454f Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sat, 10 Jan 2026 14:58:56 -0800 Subject: [PATCH 108/111] implement the examine_shower_1 --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 2 +- clus/src/NeutrinoShowerClustering.cxx | 409 ++++++++++++++++++++ 2 files changed, 410 insertions(+), 1 deletion(-) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 4110fede..3b0d5a91 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -142,7 +142,7 @@ namespace WireCell::Clus::PR { void calculate_shower_kinematics(std::set& showers, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, Graph& graph, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); void examine_merge_showers(std::set& showers, VertexPtr main_vertex, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, Graph& graph, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); void shower_clustering_in_other_clusters(Graph& graph, VertexPtr main_vertex, std::set& showers, Facade::Cluster* main_cluster, std::vector& other_clusters, std::map map_cluster_main_vertices, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); - + void examine_shower_1(Graph& graph, VertexPtr main_vertex, std::set& showers, Facade::Cluster* main_cluster, std::vector& other_clusters, std::map map_cluster_main_vertices, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); // deghost related functions ... void order_clusters(Graph& graph, std::vector& ordered_clusters, std::map >& map_cluster_to_segments, std::map& map_cluster_total_length); diff --git a/clus/src/NeutrinoShowerClustering.cxx b/clus/src/NeutrinoShowerClustering.cxx index 5a6fd510..6f242bed 100644 --- a/clus/src/NeutrinoShowerClustering.cxx +++ b/clus/src/NeutrinoShowerClustering.cxx @@ -1564,4 +1564,413 @@ void PatternAlgorithms::shower_clustering_in_other_clusters(Graph& graph, Vertex update_shower_maps(showers, map_vertex_in_shower, map_segment_in_shower, map_vertex_to_shower, used_shower_clusters); +} + + +void PatternAlgorithms::examine_shower_1(Graph& graph, VertexPtr main_vertex, std::set& showers, Facade::Cluster* main_cluster, std::vector& other_clusters, std::map map_cluster_main_vertices, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model){ + + if (!main_vertex) return; + + // Build map_vertex_segments + std::map> map_vertex_segments; + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr seg = graph[*eit].segment; + if (!seg) continue; + + auto source_vdesc = boost::source(*eit, graph); + auto target_vdesc = boost::target(*eit, graph); + VertexPtr v1 = graph[source_vdesc].vertex; + VertexPtr v2 = graph[target_vdesc].vertex; + + if (v1) map_vertex_segments[v1].insert(seg); + if (v2) map_vertex_segments[v2].insert(seg); + } + + // Check if there is already a large EM shower connecting to main_vertex + bool flag_skip = false; + auto it = map_vertex_to_shower.find(main_vertex); + if (it != map_vertex_to_shower.end()) { + for (auto shower : it->second) { + if (shower->start_segment() && shower->start_segment()->has_particle_info() && + shower->start_segment()->particle_info()->pdg() != 11) continue; + + auto [start_vtx, conn_type] = shower->get_start_vertex_and_type(); + if (conn_type != 1) continue; + + double energy = 0; + if (shower->get_kine_best() != 0) { + energy = shower->get_kine_best(); + } else { + energy = shower->get_kine_charge(); + } + if (energy > 80 * units::MeV) flag_skip = true; + } + } + + bool flag_added = false; + + if (!flag_skip) { + std::set used_showers; + std::map> map_segment_showers; + std::map map_segment_new_shower; + std::set used_segments; + std::set del_showers; + + // Loop over segments at main_vertex + if (map_vertex_segments.find(main_vertex) != map_vertex_segments.end()) { + WireCell::Point main_vtx_pt = main_vertex->fit().valid() ? main_vertex->fit().point : main_vertex->wcpt().point; + + for (auto sg : map_vertex_segments[main_vertex]) { + // Skip strong direction segments or certain particle types + if (!sg->dir_weak()) continue; + + int particle_type = 0; + if (sg->has_particle_info() && sg->particle_info()) { + particle_type = sg->particle_info()->pdg(); + } + + double medium_dQ_dx = segment_median_dQ_dx(sg); + if (particle_type == 2212 && medium_dQ_dx / (43e3 / units::cm) > 1.6) continue; + if (particle_type == 11) continue; + + // Form a new shower + ShowerPtr shower1 = std::make_shared(graph); + shower1->set_start_vertex(main_vertex, 1); + shower1->set_start_segment(sg); + shower1->complete_structure_with_start_segment(used_segments); + + WireCell::Vector dir1 = segment_cal_dir_3vector(sg, main_vtx_pt, 15 * units::cm); + + // Check against existing showers + for (auto shower : showers) { + double energy = shower->get_kine_charge(); + double min_dis = shower_get_closest_dis(*shower, sg); + + auto [start_vtx, conn_type] = shower->get_start_vertex_and_type(); + + if (conn_type > 2 && min_dis > 3 * units::cm) continue; + if (shower->start_segment() && shower->start_segment()->has_particle_info() && + shower->start_segment()->particle_info()->pdg() != 11) continue; + if (start_vtx == main_vertex && conn_type == 1) continue; + + // Get shower1's vertices + TrajectoryView& traj1 = shower1->fill_maps(); + std::set shower1_vertices; + for (auto vdesc : traj1.nodes()) { + auto vtx = traj1.view_graph()[vdesc].vertex; + if (vtx) shower1_vertices.insert(vtx); + } + + if (conn_type == 1 && energy > 80 * units::MeV) { + if (shower1_vertices.find(start_vtx) == shower1_vertices.end()) continue; + else { + WireCell::Vector dir3 = shower_cal_dir_3vector(*shower, shower->get_start_point(), 15 * units::cm); + double angle = std::acos(std::clamp(dir3.dot(dir1) / (dir3.magnitude() * dir1.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + if (angle > 30) continue; + } + } else { + WireCell::Vector dir3 = shower_cal_dir_3vector(*shower, shower->get_start_point(), 15 * units::cm); + + // Find closest vertex in shower1 to shower start point + WireCell::Point min_point; + double min_vtx_dis = 1e9; + for (auto vtx3 : shower1_vertices) { + WireCell::Point vtx3_pt = vtx3->fit().valid() ? vtx3->fit().point : vtx3->wcpt().point; + double dis = (vtx3_pt - shower->get_start_point()).magnitude(); + if (dis < min_vtx_dis) { + min_vtx_dis = dis; + min_point = vtx3_pt; + } + } + + WireCell::Vector dir4(shower->get_start_point().x() - min_point.x(), + shower->get_start_point().y() - min_point.y(), + shower->get_start_point().z() - min_point.z()); + + double angle3 = std::acos(std::clamp(dir3.dot(dir1) / (dir3.magnitude() * dir1.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + double angle4 = std::acos(std::clamp(dir4.dot(dir1) / (dir4.magnitude() * dir1.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + double tmp_angle = std::min(angle3, angle4); + + if (energy > 25 * units::MeV && tmp_angle > 40) continue; + } + + if (used_showers.find(shower) != used_showers.end()) continue; + + auto [shower_dis, shower_point] = shower_get_closest_point(*shower, main_vtx_pt); + WireCell::Vector dir2(shower_point.x() - main_vtx_pt.x(), + shower_point.y() - main_vtx_pt.y(), + shower_point.z() - main_vtx_pt.z()); + + double angle = std::acos(std::clamp(dir1.dot(dir2) / (dir1.magnitude() * dir2.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + + if ((angle < 15 && min_dis < 36 * units::cm) || + (angle < 10 && min_dis < 46 * units::cm) || + (angle < 7.5)) { + map_segment_showers[sg].insert(shower); + used_showers.insert(shower); + } + } + + if (map_segment_showers.find(sg) != map_segment_showers.end()) { + map_segment_new_shower[sg] = shower1; + } else { + // Shower1 will be deleted by shared_ptr + } + } + } + + // Process segments with associated showers + for (auto& [sg, associated_showers] : map_segment_showers) { + ShowerPtr shower1 = map_segment_new_shower[sg]; + int num_showers = associated_showers.size(); + + double max_energy = 0; + double total_energy = 0; + for (auto shower : associated_showers) { + double energy = shower->get_kine_charge(); + if (energy > max_energy) max_energy = energy; + total_energy += energy; + } + + // Analyze shower1 structure + TrajectoryView& traj = shower1->fill_maps(); + std::set shower1_vertices; + for (auto vdesc : traj.nodes()) { + auto vtx = traj.view_graph()[vdesc].vertex; + if (vtx) shower1_vertices.insert(vtx); + } + + double max_length = 0; + SegmentPtr max_sg = nullptr; + int n_tracks = 0; + int n_showers = 0; + double total_length = 0; + bool flag_good_track = false; + + for (auto edesc : traj.edges()) { + auto sg1 = traj.view_graph()[edesc].segment; + if (!sg1) continue; + + double length = segment_track_length(sg1); + double medium_dQ_dx = segment_median_dQ_dx(sg1) / (43e3 / units::cm); + + if (!sg1->dir_weak()) { + // Find end vertex + auto seg_edesc = sg1->get_descriptor(); + auto source_vdesc = boost::source(seg_edesc, graph); + auto target_vdesc = boost::target(seg_edesc, graph); + VertexPtr v1 = graph[source_vdesc].vertex; + VertexPtr v2 = graph[target_vdesc].vertex; + + VertexPtr end_vertex = nullptr; + if (sg1->dirsign() == 1) { + if (!sg1->fits().empty() && v1 && v2) { + auto& fits = sg1->fits(); + double dist1 = (fits.back().point - (v1->fit().valid() ? v1->fit().point : v1->wcpt().point)).magnitude(); + double dist2 = (fits.back().point - (v2->fit().valid() ? v2->fit().point : v2->wcpt().point)).magnitude(); + end_vertex = (dist1 < dist2) ? v1 : v2; + } + } else if (sg1->dirsign() == -1) { + if (!sg1->fits().empty() && v1 && v2) { + auto& fits = sg1->fits(); + double dist1 = (fits.front().point - (v1->fit().valid() ? v1->fit().point : v1->wcpt().point)).magnitude(); + double dist2 = (fits.front().point - (v2->fit().valid() ? v2->fit().point : v2->wcpt().point)).magnitude(); + end_vertex = (dist1 < dist2) ? v1 : v2; + } + } + + if (end_vertex && map_vertex_segments.find(end_vertex) != map_vertex_segments.end()) { + if (map_vertex_segments[end_vertex].size() > 1) { + bool flag_non_ele = false; + for (auto sg2 : map_vertex_segments[end_vertex]) { + if (sg2 == sg1) continue; + bool is_shower = sg2->flags_any(SegmentFlags::kShowerTrajectory) || + sg2->flags_any(SegmentFlags::kShowerTopology); + if (!is_shower) flag_non_ele = true; + } + if (!flag_non_ele && map_vertex_segments[end_vertex].size() <= 3) { + flag_good_track = true; + } + } else { + flag_good_track = true; + } + } + } + + bool is_shower = sg1->flags_any(SegmentFlags::kShowerTrajectory) || + sg1->flags_any(SegmentFlags::kShowerTopology); + if (is_shower) n_showers++; + + n_tracks++; + total_length += length; + if (max_length < length) { + max_length = length; + max_sg = sg1; + } + (void)medium_dQ_dx; // To avoid unused variable warning + } + (void)flag_good_track; + (void)n_showers; + + // Check if should skip + bool flag_skip_segment = false; + for (auto shower_check : showers) { + auto [start_vtx_check, conn_type_check] = shower_check->get_start_vertex_and_type(); + if (conn_type_check == 1 && associated_showers.find(shower_check) == associated_showers.end()) { + if (shower1_vertices.find(start_vtx_check) != shower1_vertices.end() && + start_vtx_check != main_vertex && + shower_check->get_kine_charge() > 60 * units::MeV) { + flag_skip_segment = true; + } + } + } + + // Decide whether to create this shower + if (total_length < 70 * units::cm && + ((n_tracks == 1 && total_length < 60 * units::cm) || + (n_tracks == 1 && total_length < 65 * units::cm && num_showers > 3 && total_energy > 150 * units::MeV) || + total_length < n_tracks * 36 * units::cm) && + (total_energy > 50 * units::MeV || total_energy / units::MeV > total_length / units::cm * 0.75) && + !flag_skip_segment) { + + // Set particle type to electron + if (shower1->start_segment() && shower1->start_segment()->has_particle_info() && + shower1->start_segment()->particle_info()) { + shower1->start_segment()->particle_info()->set_pdg(11); + } + shower1->start_segment()->set_flags(SegmentFlags::kAvoidMuonCheck); + shower1->update_particle_type(particle_data, recomb_model); + + // Merge associated showers + for (auto shower : associated_showers) { + del_showers.insert(shower); + shower1->add_shower(*shower); + } + + shower1->calculate_kinematics(particle_data, recomb_model); + double kine_charge = cal_kine_charge(shower1, graph, track_fitter, dv); + shower1->set_kine_charge(kine_charge); + shower1->set_flag_kinematics(true); + + showers.insert(shower1); + std::cout << "Create a new low-energy shower: " << kine_charge / units::MeV << " MeV" << std::endl; + flag_added = true; + } + } + + // Remove deleted showers + for (auto shower : del_showers) { + showers.erase(shower); + } + + update_shower_maps(showers, map_vertex_in_shower, map_segment_in_shower, + map_vertex_to_shower, used_shower_clusters); + } + + // Second part: merge existing showers if nothing was added + if (!flag_added) { + std::map> map_shower_showers; + WireCell::Point main_vtx_pt = main_vertex->fit().valid() ? main_vertex->fit().point : main_vertex->wcpt().point; + + auto it = map_vertex_to_shower.find(main_vertex); + if (it != map_vertex_to_shower.end()) { + for (auto shower : it->second) { + if (shower->start_segment() && shower->start_segment()->has_particle_info() && + shower->start_segment()->particle_info()->pdg() != 11) continue; + + auto [start_vtx, conn_type] = shower->get_start_vertex_and_type(); + if (conn_type != 1) continue; + + WireCell::Vector dir1 = shower_cal_dir_3vector(*shower, main_vtx_pt, 15 * units::cm); + + for (auto shower1 : showers) { + double energy = shower1->get_kine_charge(); + double min_dis = shower_get_closest_dis(*shower1, shower->start_segment()); + + auto [start_vtx1, conn_type1] = shower1->get_start_vertex_and_type(); + + if (conn_type1 > 2 && min_dis > 3 * units::cm) continue; + if (shower1->start_segment() && shower1->start_segment()->has_particle_info() && + shower1->start_segment()->particle_info()->pdg() != 11) continue; + if (start_vtx1 == main_vertex && conn_type1 == 1) continue; + + // Get shower's vertices + TrajectoryView& traj_shower = shower->fill_maps(); + std::set shower_vertices; + for (auto vdesc : traj_shower.nodes()) { + auto vtx = traj_shower.view_graph()[vdesc].vertex; + if (vtx) shower_vertices.insert(vtx); + } + + if (conn_type1 == 1 && shower_vertices.find(start_vtx1) == shower_vertices.end()) continue; + + if (shower1->get_total_length() < 3 * units::cm) continue; + + auto [shower_dis, shower_point] = shower_get_closest_point(*shower1, main_vtx_pt); + WireCell::Vector dir2(shower_point.x() - main_vtx_pt.x(), + shower_point.y() - main_vtx_pt.y(), + shower_point.z() - main_vtx_pt.z()); + + double angle = std::acos(std::clamp(dir1.dot(dir2) / (dir1.magnitude() * dir2.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + + WireCell::Vector dir3 = shower_cal_dir_3vector(*shower1, shower1->get_start_point(), 30 * units::cm); + double angle1 = std::acos(std::clamp(dir2.dot(dir3) / (dir2.magnitude() * dir3.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + + if (angle < 15 && angle1 < 15 && min_dis < 28 * units::cm) { + map_shower_showers[shower].insert(shower1); + } + (void)energy; // To avoid unused variable warning + } + } + } + + // Find the shower combination with maximum energy + std::set del_showers; + ShowerPtr max_shower = nullptr; + double max_energy = 0; + + for (auto& [shower, associated_showers] : map_shower_showers) { + double acc_energy = 0; + if (shower->get_kine_best() != 0) { + acc_energy += shower->get_kine_best(); + } else { + acc_energy += shower->get_kine_charge(); + } + + for (auto shower1 : associated_showers) { + if (shower1->get_kine_best() != 0) { + acc_energy += shower1->get_kine_best(); + } else { + acc_energy += shower1->get_kine_charge(); + } + } + + if (acc_energy > max_energy) { + max_energy = acc_energy; + max_shower = shower; + } + } + + if (max_shower) { + for (auto shower1 : map_shower_showers[max_shower]) { + max_shower->add_shower(*shower1); + del_showers.insert(shower1); + } + + max_shower->calculate_kinematics(particle_data, recomb_model); + max_shower->start_segment()->set_flags(SegmentFlags::kAvoidMuonCheck); + double kine_charge = cal_kine_charge(max_shower, graph, track_fitter, dv); + max_shower->set_kine_charge(kine_charge); + max_shower->set_flag_kinematics(true); + } + + // Remove deleted showers + for (auto shower : del_showers) { + showers.erase(shower); + } + + update_shower_maps(showers, map_vertex_in_shower, map_segment_in_shower, + map_vertex_to_shower, used_shower_clusters); + } } \ No newline at end of file From 0997cc1c0f15f66370d890cb3231273ebb5f65e5 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sat, 10 Jan 2026 15:53:26 -0800 Subject: [PATCH 109/111] implement examine_showers --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 1 + clus/src/NeutrinoShowerClustering.cxx | 350 ++++++++++++++++++++ 2 files changed, 351 insertions(+) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 3b0d5a91..3a9c6140 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -143,6 +143,7 @@ namespace WireCell::Clus::PR { void examine_merge_showers(std::set& showers, VertexPtr main_vertex, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, Graph& graph, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); void shower_clustering_in_other_clusters(Graph& graph, VertexPtr main_vertex, std::set& showers, Facade::Cluster* main_cluster, std::vector& other_clusters, std::map map_cluster_main_vertices, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); void examine_shower_1(Graph& graph, VertexPtr main_vertex, std::set& showers, Facade::Cluster* main_cluster, std::vector& other_clusters, std::map map_cluster_main_vertices, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); + void examine_showers(Graph& graph, VertexPtr main_vertex, std::set& showers, Facade::Cluster* main_cluster, std::vector& other_clusters, std::map map_cluster_main_vertices, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); // deghost related functions ... void order_clusters(Graph& graph, std::vector& ordered_clusters, std::map >& map_cluster_to_segments, std::map& map_cluster_total_length); diff --git a/clus/src/NeutrinoShowerClustering.cxx b/clus/src/NeutrinoShowerClustering.cxx index 6f242bed..d1714a64 100644 --- a/clus/src/NeutrinoShowerClustering.cxx +++ b/clus/src/NeutrinoShowerClustering.cxx @@ -1973,4 +1973,354 @@ void PatternAlgorithms::examine_shower_1(Graph& graph, VertexPtr main_vertex, st update_shower_maps(showers, map_vertex_in_shower, map_segment_in_shower, map_vertex_to_shower, used_shower_clusters); } +} + + +void PatternAlgorithms::examine_showers(Graph& graph, VertexPtr main_vertex, std::set& showers, Facade::Cluster* main_cluster, std::vector& other_clusters, std::map map_cluster_main_vertices, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model){ + + if (!main_vertex) return; + + // Build map_vertex_segments + std::map> map_vertex_segments; + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr seg = graph[*eit].segment; + if (!seg) continue; + + auto source_vdesc = boost::source(*eit, graph); + auto target_vdesc = boost::target(*eit, graph); + VertexPtr v1 = graph[source_vdesc].vertex; + VertexPtr v2 = graph[target_vdesc].vertex; + + if (v1) map_vertex_segments[v1].insert(seg); + if (v2) map_vertex_segments[v2].insert(seg); + } + + std::map map_merge_seg_shower; + WireCell::Vector drift_dir(1, 0, 0); + std::set del_showers; + + WireCell::Point main_vtx_pt = main_vertex->fit().valid() ? main_vertex->fit().point : main_vertex->wcpt().point; + + // Loop over segments at main_vertex + if (map_vertex_segments.find(main_vertex) != map_vertex_segments.end()) { + for (auto sg : map_vertex_segments[main_vertex]) { + // Skip if already in shower and not a muon + if (map_segment_in_shower.find(sg) != map_segment_in_shower.end()) { + if (sg->has_particle_info() && sg->particle_info()->pdg() != 13) continue; + } + + double sg_length = segment_track_length(sg); + + // Skip long segments + if ((sg_length > 45 * units::cm && !sg->dir_weak()) || sg_length > 55 * units::cm) continue; + + // Find other vertex + auto seg_edesc = sg->get_descriptor(); + auto source_vdesc = boost::source(seg_edesc, graph); + auto target_vdesc = boost::target(seg_edesc, graph); + VertexPtr v1 = graph[source_vdesc].vertex; + VertexPtr v2 = graph[target_vdesc].vertex; + VertexPtr vtx = (v1 == main_vertex) ? v2 : v1; + if (!vtx) continue; + + auto daughter_result = calculate_num_daughter_showers(graph, main_vertex, sg, false); + double daughter_length = daughter_result.second; + + bool flag_checked = false; + + // Case I: Check showers at the other vertex + if (map_vertex_to_shower.find(vtx) != map_vertex_to_shower.end()) { + WireCell::Point vtx_pt = vtx->fit().valid() ? vtx->fit().point : vtx->wcpt().point; + WireCell::Vector dir1 = segment_cal_dir_3vector(sg, vtx_pt, 15 * units::cm); + WireCell::Vector dir1_1 = segment_cal_dir_3vector(sg, main_vtx_pt, 15 * units::cm); + bool flag_tmp_connected = false; + + // First pass: check for high-energy directly connected showers + for (auto shower : map_vertex_to_shower[vtx]) { + if (shower->start_segment() && shower->start_segment()->has_particle_info() && + shower->start_segment()->particle_info()->pdg() != 11) continue; + + auto [start_vtx, conn_type] = shower->get_start_vertex_and_type(); + double Eshower = (shower->get_kine_best() != 0) ? shower->get_kine_best() : shower->get_kine_charge(); + + if (conn_type == 1 && Eshower > 60 * units::MeV) flag_tmp_connected = true; + } + + // Second pass: check merging conditions + for (auto shower : map_vertex_to_shower[vtx]) { + if (shower->start_segment() && shower->start_segment()->has_particle_info() && + shower->start_segment()->particle_info()->pdg() != 11) continue; + + auto [start_vtx, conn_type] = shower->get_start_vertex_and_type(); + WireCell::Vector dir2 = shower_cal_dir_3vector(*shower, shower->get_start_point(), 100 * units::cm); + + double Eshower = (shower->get_kine_best() != 0) ? shower->get_kine_best() : shower->get_kine_charge(); + + // Check shower composition + TrajectoryView& traj = shower->fill_maps(); + double tmp_total_length = 0; + double tmp_track_length = 0; + + for (auto edesc : traj.edges()) { + auto sg1 = traj.view_graph()[edesc].segment; + if (!sg1) continue; + if (sg1->cluster() != shower->start_segment()->cluster()) continue; + + double length = segment_track_length(sg1); + tmp_total_length += length; + if (!sg1->dir_weak()) tmp_track_length += length; + } + + if (tmp_track_length > 3 * units::cm && tmp_track_length > 0.25 * tmp_total_length) continue; + + if (conn_type == 1 && Eshower > 100 * units::MeV) flag_checked = true; + + double angle1 = std::acos(std::clamp(dir1.dot(dir2) / (dir1.magnitude() * dir2.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + double angle2 = std::acos(std::clamp(dir1_1.dot(dir2) / (dir1_1.magnitude() * dir2.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + double tmp_angle = std::min(180.0 - angle1, angle2); + + auto [closest_dis, closest_pt] = segment_get_closest_point(sg, shower->get_start_point()); + + // Special checks for connection type 2 + if (conn_type == 2) { + if ((!sg->dir_weak() && sg_length > 3 * units::cm) || flag_tmp_connected) { + if (closest_dis < 8 * units::cm && Eshower > 75 * units::MeV && tmp_angle < 6) { + // Allow this condition + } else { + continue; + } + } + if (closest_dis > 20 * units::cm && Eshower < 150 * units::MeV && tmp_angle > 2.5) continue; + } + + // Main merging conditions + if ((Eshower > 800 * units::MeV && tmp_angle < 30) || + (Eshower > 150 * units::MeV && tmp_angle < 10) || + (Eshower > 150 * units::MeV && tmp_angle < 18 && conn_type == 1 && sg->dir_weak()) || + (Eshower > 100 * units::MeV && tmp_angle < 10 && sg_length < 25 * units::cm) || + (Eshower > 250 * units::MeV && tmp_angle < 15) || + (Eshower > 360 * units::MeV && tmp_angle < 25) || + (Eshower > 100 * units::MeV && Eshower <= 150 * units::MeV && tmp_angle < 15 && + sg_length < 25 * units::cm && flag_checked) || + (Eshower > 60 * units::MeV && conn_type == 2 && sg->dir_weak() && + ((tmp_angle < 15 && closest_dis < 18 * units::cm) || + (tmp_angle < 17.5 && closest_dis < 6 * units::cm)) && + sg_length < 15 * units::cm) || + (Eshower > 60 * units::MeV && conn_type == 2 && tmp_angle < 7.5 && + closest_dis < 8 * units::cm && sg_length < 20 * units::cm)) { + map_merge_seg_shower[sg] = shower; + continue; + } + } + } + + if (map_merge_seg_shower.find(sg) != map_merge_seg_shower.end()) continue; + if (flag_checked) continue; + if (!sg->dir_weak() && sg_length > 6 * units::cm && daughter_length < 40 * units::cm) continue; + + // Case II: Check showers at main_vertex (not directly connected) + if (map_vertex_to_shower.find(main_vertex) != map_vertex_to_shower.end()) { + WireCell::Vector dir1 = segment_cal_dir_3vector(sg, main_vtx_pt, 15 * units::cm); + + for (auto shower : map_vertex_to_shower[main_vertex]) { + if (shower->start_segment() && shower->start_segment()->has_particle_info() && + shower->start_segment()->particle_info()->pdg() != 11) continue; + + auto [start_vtx, conn_type] = shower->get_start_vertex_and_type(); + if (conn_type == 1) continue; // Skip directly connected + + WireCell::Vector dir2(shower->get_start_point().x() - main_vtx_pt.x(), + shower->get_start_point().y() - main_vtx_pt.y(), + shower->get_start_point().z() - main_vtx_pt.z()); + WireCell::Vector dir3 = shower_cal_dir_3vector(*shower, shower->get_start_point(), 100 * units::cm); + + auto [min_dis, closest_pt] = segment_get_closest_point(sg, shower->get_start_point()); + + double angle_dir2 = std::acos(std::clamp(dir1.dot(dir2) / (dir1.magnitude() * dir2.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + double angle_dir3 = std::acos(std::clamp(dir1.dot(dir3) / (dir1.magnitude() * dir3.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + double angle_drift1 = std::acos(std::clamp(dir1.dot(drift_dir) / (dir1.magnitude() * drift_dir.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + double angle_drift3 = std::acos(std::clamp(dir3.dot(drift_dir) / (dir3.magnitude() * drift_dir.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + + if (((shower->get_kine_charge() > 80 * units::MeV && angle_dir2 < 10) || + (shower->get_kine_charge() > 50 * units::MeV && angle_dir2 < 3) || + (shower->get_kine_charge() > 80 * units::MeV && angle_dir3 < 6 && angle_dir2 < 17.5) || + (shower->get_kine_charge() > 80 * units::MeV && angle_dir3 < 6 && + std::fabs(90 - angle_drift1) < 10 && std::fabs(90 - angle_drift3) < 10 && angle_dir2 < 30)) && + (sg_length > 5 * units::cm || (sg_length > 3 * units::cm && min_dis < 2.0 * units::cm))) { + map_merge_seg_shower[sg] = shower; + continue; + } + } + } + + // Case III: Check other showers + WireCell::Vector dir1 = segment_cal_dir_3vector(sg, main_vtx_pt, 15 * units::cm); + + for (auto shower : showers) { + auto [start_vtx_shower, conn_type_shower] = shower->get_start_vertex_and_type(); + if (start_vtx_shower == vtx || start_vtx_shower == main_vertex) continue; + + if (shower->start_segment() && shower->start_segment()->has_particle_info() && + shower->start_segment()->particle_info()->pdg() != 11) continue; + + if (conn_type_shower <= 2) { + WireCell::Vector dir2(shower->get_start_point().x() - main_vtx_pt.x(), + shower->get_start_point().y() - main_vtx_pt.y(), + shower->get_start_point().z() - main_vtx_pt.z()); + WireCell::Vector dir3 = shower_cal_dir_3vector(*shower, shower->get_start_point(), 100 * units::cm); + + double angle_dir2 = std::acos(std::clamp(dir1.dot(dir2) / (dir1.magnitude() * dir2.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + double angle_dir3 = std::acos(std::clamp(dir2.dot(dir3) / (dir2.magnitude() * dir3.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + + if ((((shower->get_kine_charge() > 80 * units::MeV && angle_dir2 < 15) || + (shower->get_kine_charge() > 50 * units::MeV && angle_dir2 < 5)) && + angle_dir3 < 15 && (angle_dir2 + angle_dir3) < 25 && sg_length > 5 * units::cm)) { + map_merge_seg_shower[sg] = shower; + continue; + } + } + } + } + } + + // Mark showers for deletion if their start segment should be merged + for (auto shower : showers) { + SegmentPtr sg1 = shower->start_segment(); + if (sg1 && map_merge_seg_shower.find(sg1) != map_merge_seg_shower.end()) { + sg1->set_flags(SegmentFlags::kAvoidMuonCheck); + del_showers.insert(shower); + } + } + + // Delete marked showers first + if (del_showers.size() != 0) { + for (auto shower1 : del_showers) { + showers.erase(shower1); + } + del_showers.clear(); + update_shower_maps(showers, map_vertex_in_shower, map_segment_in_shower, + map_vertex_to_shower, used_shower_clusters); + } + + // Perform the merging + std::set updated_showers; + for (auto& [sg, shower] : map_merge_seg_shower) { + std::cout << "EM shower modification: " << shower->start_segment()->id() << " -> " << sg->id() << std::endl; + updated_showers.insert(shower); + + auto [pair_vertex, pair_conn_type] = shower->get_start_vertex_and_type(); + + if (pair_conn_type != 1) { + shower->add_segment(sg); + shower->set_start_vertex(main_vertex, 1); + shower->set_start_segment(sg); + shower->set_start_point(main_vtx_pt); + std::set tmp_used_segments; + shower->complete_structure_with_start_segment(tmp_used_segments); + if (segment_track_length(sg) > 44 * units::cm || sg->dir_weak()) { + sg->set_flags(SegmentFlags::kAvoidMuonCheck); + } + } else { + shower->add_segment(sg); + shower->set_start_vertex(main_vertex, 1); + shower->set_start_segment(sg); + shower->set_start_point(main_vtx_pt); + std::set tmp_used_segments; + shower->complete_structure_with_start_segment(tmp_used_segments); + if (shower->get_num_main_segments() >= 3) { + sg->set_flags(SegmentFlags::kAvoidMuonCheck); + } + } + + // Add other showers that connect to this shower + TrajectoryView& traj = shower->fill_maps(); + std::set shower_vertices; + for (auto vdesc : traj.nodes()) { + auto vtx = traj.view_graph()[vdesc].vertex; + if (vtx) shower_vertices.insert(vtx); + } + + for (auto shower1 : showers) { + if (shower == shower1) continue; + auto [start_vtx1, conn_type1] = shower1->get_start_vertex_and_type(); + if (conn_type1 == 1 && start_vtx1 != main_vertex) { + if (shower_vertices.find(start_vtx1) != shower_vertices.end()) { + shower->add_shower(*shower1); + del_showers.insert(shower1); + } + } + } + + // Update particle type and kinematics + if (sg->has_particle_info() && sg->particle_info()) { + sg->particle_info()->set_pdg(11); + } + shower->update_particle_type(particle_data, recomb_model); + shower->calculate_kinematics(particle_data, recomb_model); + double kine_charge = cal_kine_charge(shower, graph, track_fitter, dv); + shower->set_kine_charge(kine_charge); + shower->set_flag_kinematics(true); + } + + // Delete merged showers (not at main_vertex) + for (auto shower1 : del_showers) { + auto [start_vtx1, conn_type1] = shower1->get_start_vertex_and_type(); + if (start_vtx1 != main_vertex) { + showers.erase(shower1); + } + } + del_showers.clear(); + + // Check other showers and merge with updated showers + for (auto shower : updated_showers) { + WireCell::Point shower_start = shower->get_start_point(); + WireCell::Vector dir1 = shower_cal_dir_3vector(*shower, shower_start, 25 * units::cm); + + for (auto shower1 : showers) { + if (updated_showers.find(shower1) != updated_showers.end()) continue; + if (shower1->start_segment() && shower1->start_segment()->has_particle_info() && + shower1->start_segment()->particle_info()->pdg() != 11) continue; + + auto [start_vtx1, conn_type1] = shower1->get_start_vertex_and_type(); + if (conn_type1 == 2) { + if (del_showers.find(shower1) != del_showers.end()) continue; + + auto [shower_vtx, shower_conn] = shower->get_start_vertex_and_type(); + WireCell::Point shower_vtx_pt = shower_vtx->fit().valid() ? shower_vtx->fit().point : shower_vtx->wcpt().point; + + WireCell::Vector dir2(shower1->get_start_point().x() - shower_vtx_pt.x(), + shower1->get_start_point().y() - shower_vtx_pt.y(), + shower1->get_start_point().z() - shower_vtx_pt.z()); + WireCell::Vector dir3 = shower_cal_dir_3vector(*shower1, shower1->get_start_point(), 25 * units::cm); + + double angle_dir2 = std::acos(std::clamp(dir1.dot(dir2) / (dir1.magnitude() * dir2.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + double angle_dir3 = std::acos(std::clamp(dir1.dot(dir3) / (dir1.magnitude() * dir3.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + + if (angle_dir2 < 10 && angle_dir3 < 20) { + shower->add_shower(*shower1); + shower->calculate_kinematics(particle_data, recomb_model); + double kine_charge = cal_kine_charge(shower, graph, track_fitter, dv); + shower->set_kine_charge(kine_charge); + shower->set_flag_kinematics(true); + del_showers.insert(shower1); + } + } + } + } + + // Final deletion + for (auto shower1 : del_showers) { + showers.erase(shower1); + } + + if (map_merge_seg_shower.size() > 0) { + update_shower_maps(showers, map_vertex_in_shower, map_segment_in_shower, + map_vertex_to_shower, used_shower_clusters); + } + + // Finally call examine_shower_1 + examine_shower_1(graph, main_vertex, showers, main_cluster, other_clusters, map_cluster_main_vertices, + map_vertex_in_shower, map_segment_in_shower, map_vertex_to_shower, used_shower_clusters, + track_fitter, dv, particle_data, recomb_model); } \ No newline at end of file From 189175f0ddbfa511dd941ecd6756fe23d92e3799 Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sat, 10 Jan 2026 16:56:40 -0800 Subject: [PATCH 110/111] implement the id_pi0_without_vertex function --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 3 + clus/src/NeutrinoShowerClustering.cxx | 566 +++++++++++++++++++- util/inc/WireCellUtil/Point.h | 12 + util/src/Point.cxx | 86 +++ 4 files changed, 666 insertions(+), 1 deletion(-) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 3a9c6140..94a65a3d 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -144,6 +144,9 @@ namespace WireCell::Clus::PR { void shower_clustering_in_other_clusters(Graph& graph, VertexPtr main_vertex, std::set& showers, Facade::Cluster* main_cluster, std::vector& other_clusters, std::map map_cluster_main_vertices, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); void examine_shower_1(Graph& graph, VertexPtr main_vertex, std::set& showers, Facade::Cluster* main_cluster, std::vector& other_clusters, std::map map_cluster_main_vertices, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); void examine_showers(Graph& graph, VertexPtr main_vertex, std::set& showers, Facade::Cluster* main_cluster, std::vector& other_clusters, std::map map_cluster_main_vertices, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); + void id_pi0_with_vertex(int acc_segment_id, std::set& pi0_showers, std::map& map_shower_pio_id, std::map >& map_pio_id_showers, std::map >& map_pio_id_mass, std::map >& map_pio_id_saved_pair, Graph& graph, VertexPtr main_vertex, std::set& showers, Facade::Cluster* main_cluster, std::vector& other_clusters, std::map map_cluster_main_vertices, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); + void id_pi0_without_vertex(int acc_segment_id, std::set& pi0_showers, std::map& map_shower_pio_id, std::map >& map_pio_id_showers, std::map >& map_pio_id_mass, std::map >& map_pio_id_saved_pair, Graph& graph, VertexPtr main_vertex, std::set& showers, Facade::Cluster* main_cluster, std::vector& other_clusters, std::map map_cluster_main_vertices, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); + // deghost related functions ... void order_clusters(Graph& graph, std::vector& ordered_clusters, std::map >& map_cluster_to_segments, std::map& map_cluster_total_length); diff --git a/clus/src/NeutrinoShowerClustering.cxx b/clus/src/NeutrinoShowerClustering.cxx index d1714a64..c5d6e1bd 100644 --- a/clus/src/NeutrinoShowerClustering.cxx +++ b/clus/src/NeutrinoShowerClustering.cxx @@ -2323,4 +2323,568 @@ void PatternAlgorithms::examine_showers(Graph& graph, VertexPtr main_vertex, std examine_shower_1(graph, main_vertex, showers, main_cluster, other_clusters, map_cluster_main_vertices, map_vertex_in_shower, map_segment_in_shower, map_vertex_to_shower, used_shower_clusters, track_fitter, dv, particle_data, recomb_model); -} \ No newline at end of file +} + + +void PatternAlgorithms::id_pi0_with_vertex(int acc_segment_id, std::set& pi0_showers, std::map& map_shower_pio_id, std::map >& map_pio_id_showers, std::map >& map_pio_id_mass, std::map >& map_pio_id_saved_pair, Graph& graph, VertexPtr main_vertex, std::set& showers, Facade::Cluster* main_cluster, std::vector& other_clusters, std::map map_cluster_main_vertices, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model){ + + if (!main_vertex) return; + + // Build map_vertex_segments + std::map> map_vertex_segments; + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr seg = graph[*eit].segment; + if (!seg) continue; + + auto source_vdesc = boost::source(*eit, graph); + auto target_vdesc = boost::target(*eit, graph); + VertexPtr v1 = graph[source_vdesc].vertex; + VertexPtr v2 = graph[target_vdesc].vertex; + + if (v1) map_vertex_segments[v1].insert(seg); + if (v2) map_vertex_segments[v2].insert(seg); + } + + // Figure out all disconnected showers + std::set disconnected_showers; + std::map map_shower_dir; + + for (auto& [vtx, shower_set] : map_vertex_to_shower) { + for (auto shower : shower_set) { + auto [start_vtx, conn_type] = shower->get_start_vertex_and_type(); + + if (conn_type == 2 && std::abs(shower->get_particle_type()) != 13) { + disconnected_showers.insert(shower); + map_shower_dir[shower] = shower_cal_dir_3vector(*shower, shower->get_start_point(), 15 * units::cm); + } else if (conn_type == 1) { + WireCell::Point start_vtx_pt = start_vtx->fit().valid() ? start_vtx->fit().point : start_vtx->wcpt().point; + map_shower_dir[shower] = shower_cal_dir_3vector(*shower, start_vtx_pt, 15 * units::cm); + } + } + } + + // Build candidate vertices + std::set candidate_vertices; + candidate_vertices.insert(main_vertex); + + for (auto& [vtx, shower_set] : map_vertex_to_shower) { + bool flag_add = true; + auto it_in_shower = map_vertex_in_shower.find(vtx); + if (it_in_shower != map_vertex_in_shower.end()) { + flag_add = false; + auto [start_vtx, conn_type] = it_in_shower->second->get_start_vertex_and_type(); + if (vtx == start_vtx) flag_add = true; + } + if (flag_add) candidate_vertices.insert(vtx); + } + + // Map shower pairs to masses and vertices + std::map, std::vector>> map_shower_pair_mass_vertex; + + for (auto vtx : candidate_vertices) { + std::vector tmp_showers; + std::map local_dirs; + + WireCell::Point vtx_pt = vtx->fit().valid() ? vtx->fit().point : vtx->wcpt().point; + + // Add directly connected showers (type 1, not muon) + auto it2 = map_vertex_to_shower.find(vtx); + if (it2 != map_vertex_to_shower.end()) { + for (auto shower : it2->second) { + auto [start_vtx, conn_type] = shower->get_start_vertex_and_type(); + if (conn_type == 1 && std::abs(shower->get_particle_type()) != 13) { + tmp_showers.push_back(shower); + local_dirs[shower] = shower->get_init_dir(); + } + } + } + + // Add disconnected showers within angle + for (auto shower : disconnected_showers) { + WireCell::Vector dir1 = map_shower_dir[shower]; + WireCell::Vector dir2(shower->get_start_point().x() - vtx_pt.x(), + shower->get_start_point().y() - vtx_pt.y(), + shower->get_start_point().z() - vtx_pt.z()); + + auto [start_vtx, conn_type] = shower->get_start_vertex_and_type(); + + if (start_vtx == vtx) { + tmp_showers.push_back(shower); + local_dirs[shower] = shower->get_init_dir(); + } else { + double angle = std::acos(std::clamp(dir1.dot(dir2) / (dir1.magnitude() * dir2.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + if (angle < 30) { + tmp_showers.push_back(shower); + local_dirs[shower] = dir2; + } + } + } + + // Calculate pi0 masses for all pairs + if (tmp_showers.size() > 1) { + for (size_t i = 0; i < tmp_showers.size(); i++) { + ShowerPtr shower_1 = tmp_showers[i]; + WireCell::Vector dir1 = local_dirs[shower_1]; + + for (size_t j = i + 1; j < tmp_showers.size(); j++) { + ShowerPtr shower_2 = tmp_showers[j]; + WireCell::Vector dir2 = local_dirs[shower_2]; + + double angle = std::acos(std::clamp(dir1.dot(dir2) / (dir1.magnitude() * dir2.magnitude()), -1.0, 1.0)); + double mass_pio = std::sqrt(4 * shower_1->get_kine_charge() * shower_2->get_kine_charge() * + std::pow(std::sin(angle / 2.0), 2)); + + auto [start_vtx_1, conn_type_1] = shower_1->get_start_vertex_and_type(); + auto [start_vtx_2, conn_type_2] = shower_2->get_start_vertex_and_type(); + + if (conn_type_1 == 1 && conn_type_2 == 1) continue; + + map_shower_pair_mass_vertex[std::make_pair(shower_1, shower_2)].push_back(std::make_pair(mass_pio, vtx)); + } + } + } + } + + // Store pi0 kinematic variables (not implemented as member variables yet, just processing) + WireCell::Point main_vtx_pt = main_vertex->fit().valid() ? main_vertex->fit().point : main_vertex->wcpt().point; + + // Find pi0 iteratively + // static int acc_segment_id = 100000; // Static counter for pi0 IDs + + while (map_shower_pair_mass_vertex.size() > 0) { + double mass_diff = 1e9; + double mass_save = 0; + ShowerPtr shower_1 = nullptr; + ShowerPtr shower_2 = nullptr; + double mass_offset = 10 * units::MeV; + VertexPtr vtx = nullptr; + double mass_penalty = 0; + double tmp_mass_penalty = 0; + + // Find best pi0 candidate + for (auto& [shower_pair, mass_vtx_vec] : map_shower_pair_mass_vertex) { + for (auto& [mass, candidate_vtx] : mass_vtx_vec) { + auto [start_vtx_1, conn_type_1] = shower_pair.first->get_start_vertex_and_type(); + auto [start_vtx_2, conn_type_2] = shower_pair.second->get_start_vertex_and_type(); + + if (conn_type_1 == 2 && conn_type_2 == 2) { + tmp_mass_penalty = 6 * units::MeV; + } else { + tmp_mass_penalty = 0; + } + + if (mass - 135 * units::MeV + mass_offset < 35 * units::MeV && + mass - 135 * units::MeV + mass_offset > -25 * units::MeV) { + if (std::abs(mass - 135 * units::MeV + mass_offset) - tmp_mass_penalty < + std::abs(mass_diff) - mass_penalty) { + mass_diff = mass - 135 * units::MeV + mass_offset; + mass_penalty = tmp_mass_penalty; + mass_save = mass; + shower_1 = shower_pair.first; + shower_2 = shower_pair.second; + vtx = candidate_vtx; + } + } + } + } + + // If found a good pi0, mark it + if (mass_diff < 35 * units::MeV && mass_diff > -25 * units::MeV) { + pi0_showers.insert(shower_1); + pi0_showers.insert(shower_2); + + int pio_id = acc_segment_id; + acc_segment_id++; + + map_shower_pio_id[shower_1] = pio_id; + map_shower_pio_id[shower_2] = pio_id; + map_pio_id_mass[pio_id] = std::make_pair(mass_save, 1); + map_pio_id_showers[pio_id].push_back(shower_1); + map_pio_id_showers[pio_id].push_back(shower_2); + + // Update shower start vertices if needed + auto [start_vtx_1, conn_type_1] = shower_1->get_start_vertex_and_type(); + if (start_vtx_1 != vtx) { + shower_1->set_start_vertex(vtx, 2); + shower_1->calculate_kinematics(particle_data, recomb_model); + } + + auto [start_vtx_2, conn_type_2] = shower_2->get_start_vertex_and_type(); + if (start_vtx_2 != vtx) { + shower_2->set_start_vertex(vtx, 2); + shower_2->calculate_kinematics(particle_data, recomb_model); + } + + std::cout << "Pi0 found with mass: " << mass_save / units::MeV << " MeV with " + << shower_1->get_kine_charge() / units::MeV << " MeV + " + << shower_2->get_kine_charge() / units::MeV << " MeV" << std::endl; + } else { + break; + } + + // Remove pairs involving these showers + std::vector> to_be_removed; + for (auto& [shower_pair, mass_vtx_vec] : map_shower_pair_mass_vertex) { + if (shower_pair.first == shower_1 || shower_pair.first == shower_2 || + shower_pair.second == shower_1 || shower_pair.second == shower_2) { + to_be_removed.push_back(shower_pair); + } + } + for (auto& pair : to_be_removed) { + map_shower_pair_mass_vertex.erase(pair); + } + } + + // Find pi0 vertices and change incoming muons to pions + std::set pi0_vertices; + for (auto shower : pi0_showers) { + auto [start_vtx, conn_type] = shower->get_start_vertex_and_type(); + pi0_vertices.insert(start_vtx); + } + + for (auto vtx : pi0_vertices) { + if (map_vertex_segments.find(vtx) == map_vertex_segments.end()) continue; + + WireCell::Point vtx_pt = vtx->fit().valid() ? vtx->fit().point : vtx->wcpt().point; + + for (auto sg : map_vertex_segments[vtx]) { + // Determine if segment starts or ends at this vertex + bool flag_start = false; + if (!sg->fits().empty()) { + double dist_front = (sg->fits().front().point - vtx_pt).magnitude(); + double dist_back = (sg->fits().back().point - vtx_pt).magnitude(); + flag_start = (dist_front < dist_back); + } + + int dirsign_val = sg->dirsign(); + + // Check if segment is coming in (opposite direction) + bool is_incoming = (flag_start && dirsign_val == -1) || (!flag_start && dirsign_val == 1); + + if (is_incoming && sg->has_particle_info()) { + int pdg = sg->particle_info()->pdg(); + if (std::abs(pdg) == 13 || pdg == 0) { + // Change muon to pion + sg->particle_info()->set_pdg(211); + sg->particle_info()->set_mass(139.57 * units::MeV); // Pion mass + } + } + } + } +} + + +void PatternAlgorithms::id_pi0_without_vertex(int acc_segment_id, std::set& pi0_showers, std::map& map_shower_pio_id, std::map >& map_pio_id_showers, std::map >& map_pio_id_mass, std::map >& map_pio_id_saved_pair, Graph& graph, VertexPtr main_vertex, std::set& showers, Facade::Cluster* main_cluster, std::vector& other_clusters, std::map map_cluster_main_vertices, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model){ + + if (!main_vertex) return; + + // Build map_vertex_segments and segments_in_long_muon + std::map> map_vertex_segments; + std::set segments_in_long_muon; // Placeholder - would need proper implementation + + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr seg = graph[*eit].segment; + if (!seg) continue; + + auto source_vdesc = boost::source(*eit, graph); + auto target_vdesc = boost::target(*eit, graph); + VertexPtr v1 = graph[source_vdesc].vertex; + VertexPtr v2 = graph[target_vdesc].vertex; + + if (v1) map_vertex_segments[v1].insert(seg); + if (v2) map_vertex_segments[v2].insert(seg); + } + + // Check main vertex conditions + if (map_vertex_segments[main_vertex].size() > 2) return; + + if (map_vertex_segments[main_vertex].size() > 0) { + auto first_seg = *map_vertex_segments[main_vertex].begin(); + auto last_seg = *map_vertex_segments[main_vertex].rbegin(); + + if ((map_segment_in_shower.find(first_seg) == map_segment_in_shower.end() && + map_segment_in_shower.find(last_seg) == map_segment_in_shower.end()) || + segments_in_long_muon.find(first_seg) != segments_in_long_muon.end() || + segments_in_long_muon.find(last_seg) != segments_in_long_muon.end()) { + return; + } + } + + // Build good_showers set + std::set good_showers; + { + auto it = map_vertex_to_shower.find(main_vertex); + if (it != map_vertex_to_shower.end()) { + for (auto shower : it->second) { + if (pi0_showers.find(shower) != pi0_showers.end()) return; + + auto [start_vtx, conn_type] = shower->get_start_vertex_and_type(); + if (conn_type == 1) { + good_showers.insert(shower); + } + } + } + + if (good_showers.size() > 1) { + ShowerPtr max_shower = nullptr; + double max_energy = 0; + for (auto shower : good_showers) { + double energy = shower->get_kine_charge(); + if (energy > max_energy) { + max_energy = energy; + max_shower = shower; + } + } + good_showers.clear(); + if (max_shower) good_showers.insert(max_shower); + } + } + + // Check if we have exactly 2 segments at main vertex + if (map_vertex_segments[main_vertex].size() == 2) { + bool flag_return = true; + int num_showers = 0; + + for (auto sg : map_vertex_segments[main_vertex]) { + if (map_segment_in_shower.find(sg) == map_segment_in_shower.end()) { + double sg_length = segment_track_length(sg); + if (sg_length < 1.2 * units::cm && (sg->dirsign() == 0 || sg->dir_weak())) { + flag_return = false; + } + } else { + num_showers++; + } + } + + if (flag_return && num_showers == map_vertex_segments[main_vertex].size()) { + flag_return = false; + } + if (flag_return) return; + } + + // Build map of showers to rays (lines) + std::map map_shower_ray; + WireCell::Point main_vtx_pt = main_vertex->fit().valid() ? main_vertex->fit().point : main_vertex->wcpt().point; + + // Add showers from main vertex + auto it_main = map_vertex_to_shower.find(main_vertex); + if (it_main != map_vertex_to_shower.end()) { + for (auto shower : it_main->second) { + if (shower->get_particle_type() == 13) continue; + if (shower->get_total_length() < 3 * units::cm) continue; + if (pi0_showers.find(shower) != pi0_showers.end()) continue; + + WireCell::Point test_p = shower->get_start_point(); + WireCell::Vector dir = shower_cal_dir_3vector(*shower, test_p, 15 * units::cm); + WireCell::Point p2(test_p.x() + dir.x(), test_p.y() + dir.y(), test_p.z() + dir.z()); + map_shower_ray[shower] = WireCell::Ray(test_p, p2); + } + } + + // Add showers from other vertices + for (auto& [vtx, shower_set] : map_vertex_to_shower) { + if (vtx == main_vertex) continue; + + for (auto shower : shower_set) { + if (shower->get_particle_type() == 13) continue; + if (shower->get_total_length() < 3 * units::cm) continue; + if (pi0_showers.find(shower) != pi0_showers.end()) continue; + + auto [start_vtx, conn_type] = shower->get_start_vertex_and_type(); + if (conn_type != 3) continue; + + if (!shower->start_segment()->flags_any(SegmentFlags::kShowerTrajectory) && + !shower->start_segment()->flags_any(SegmentFlags::kShowerTopology)) continue; + + auto [closest_dis, test_p] = shower_get_closest_point(*shower, main_vtx_pt); + WireCell::Vector dir = shower_cal_dir_3vector(*shower, test_p, 15 * units::cm); + WireCell::Point p2(test_p.x() + dir.x(), test_p.y() + dir.y(), test_p.z() + dir.z()); + map_shower_ray[shower] = WireCell::Ray(test_p, p2); + } + } + + if (map_shower_ray.size() > 1) { + // Calculate pi0 masses for shower pairs + std::map, std::pair> map_shower_pair_mass_point; + + for (auto it = map_shower_ray.begin(); it != map_shower_ray.end(); it++) { + ShowerPtr shower_1 = it->first; + WireCell::Ray ray1 = it->second; + double length_1 = shower_1->get_total_length(); + + for (auto it1 = it; it1 != map_shower_ray.end(); it1++) { + ShowerPtr shower_2 = it1->first; + if (shower_1 == shower_2) continue; + + WireCell::Ray ray2 = it1->second; + if (ray1.first == ray2.first) continue; + + double length_2 = shower_2->get_total_length(); + + WireCell::Vector dir1 = ray_vector(ray1); + WireCell::Vector dir2 = ray_vector(ray2); + double angle_between = std::acos(std::clamp(dir1.dot(dir2) / (dir1.magnitude() * dir2.magnitude()), -1.0, 1.0)); + if (angle_between == 0) continue; + + auto [p1_closest, p2_closest] = ray_closest_points(ray1, ray2); + WireCell::Point center((p1_closest.x() + p2_closest.x()) / 2.0, + (p1_closest.y() + p2_closest.y()) / 2.0, + (p1_closest.z() + p2_closest.z()) / 2.0); + + if (length_1 > 15 * units::cm && length_2 > 15 * units::cm) { + WireCell::Vector dir_to_shower1(ray1.first.x() - center.x(), + ray1.first.y() - center.y(), + ray1.first.z() - center.z()); + WireCell::Vector dir_to_shower2(ray2.first.x() - center.x(), + ray2.first.y() - center.y(), + ray2.first.z() - center.z()); + + if (dir_to_shower1.magnitude() < 3 * units::cm) dir_to_shower1 = dir1; + if (dir_to_shower2.magnitude() < 3 * units::cm) dir_to_shower2 = dir2; + + double angle1 = std::acos(std::clamp(dir_to_shower1.dot(dir1) / (dir_to_shower1.magnitude() * dir1.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + double angle2 = std::acos(std::clamp(dir_to_shower2.dot(dir2) / (dir_to_shower2.magnitude() * dir2.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + + if (angle1 > 25 || angle2 > 25) continue; + + double angle = std::acos(std::clamp(dir_to_shower1.dot(dir_to_shower2) / (dir_to_shower1.magnitude() * dir_to_shower2.magnitude()), -1.0, 1.0)); + double mass_pio = std::sqrt(4 * shower_1->get_kine_charge() * shower_2->get_kine_charge() * + std::pow(std::sin(angle / 2.0), 2)); + map_shower_pair_mass_point[std::make_pair(shower_1, shower_2)] = std::make_pair(mass_pio, center); + + } else if (length_1 > 15 * units::cm || length_2 > 15 * units::cm) { + WireCell::Vector dir_to_c1, dir_to_c2; + + if (length_1 > length_2) { + center = WireCell::Point((p1_closest.x() + p2_closest.x()) / 2.0, + (p1_closest.y() + p2_closest.y()) / 2.0, + (p1_closest.z() + p2_closest.z()) / 2.0); + + auto [dis2, test_p] = shower_get_closest_point(*shower_2, center); + WireCell::Vector dir3 = shower_cal_dir_3vector(*shower_2, test_p, 15 * units::cm); + WireCell::Point p3(test_p.x() + dir3.x(), test_p.y() + dir3.y(), test_p.z() + dir3.z()); + WireCell::Ray ray3(test_p, p3); + + auto [new_p1, new_p2] = ray_closest_points(ray1, ray3); + center = new_p1; + + dir_to_c1 = WireCell::Vector(ray1.first.x() - center.x(), + ray1.first.y() - center.y(), + ray1.first.z() - center.z()); + dir_to_c2 = WireCell::Vector(test_p.x() - center.x(), + test_p.y() - center.y(), + test_p.z() - center.z()); + + double angle1 = std::acos(std::clamp(dir_to_c1.dot(dir1) / (dir_to_c1.magnitude() * dir1.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + double angle2 = std::acos(std::clamp(dir_to_c2.dot(dir3) / (dir_to_c2.magnitude() * dir3.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + if (angle1 > 25 || angle2 > 25) continue; + + } else { + center = WireCell::Point((p1_closest.x() + p2_closest.x()) / 2.0, + (p1_closest.y() + p2_closest.y()) / 2.0, + (p1_closest.z() + p2_closest.z()) / 2.0); + + auto [dis1, test_p] = shower_get_closest_point(*shower_1, center); + WireCell::Vector dir3 = shower_cal_dir_3vector(*shower_1, test_p, 15 * units::cm); + WireCell::Point p3(test_p.x() + dir3.x(), test_p.y() + dir3.y(), test_p.z() + dir3.z()); + WireCell::Ray ray3(test_p, p3); + + auto [new_p1, new_p2] = ray_closest_points(ray3, ray2); + center = new_p2; + + dir_to_c2 = WireCell::Vector(ray2.first.x() - center.x(), + ray2.first.y() - center.y(), + ray2.first.z() - center.z()); + dir_to_c1 = WireCell::Vector(test_p.x() - center.x(), + test_p.y() - center.y(), + test_p.z() - center.z()); + + double angle1 = std::acos(std::clamp(dir_to_c1.dot(dir3) / (dir_to_c1.magnitude() * dir3.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + double angle2 = std::acos(std::clamp(dir_to_c2.dot(dir2) / (dir_to_c2.magnitude() * dir2.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + if (angle1 > 25 || angle2 > 25) continue; + } + + double angle = std::acos(std::clamp(dir_to_c1.dot(dir_to_c2) / (dir_to_c1.magnitude() * dir_to_c2.magnitude()), -1.0, 1.0)); + double mass_pio = std::sqrt(4 * shower_1->get_kine_charge() * shower_2->get_kine_charge() * + std::pow(std::sin(angle / 2.0), 2)); + map_shower_pair_mass_point[std::make_pair(shower_1, shower_2)] = std::make_pair(mass_pio, center); + + } else { + break; + } + } + } + + // Find best pi0 candidate + double mass_diff = 1e9; + double mass_save = 0; + ShowerPtr shower_1 = nullptr; + ShowerPtr shower_2 = nullptr; + double mass_offset = 10 * units::MeV; + WireCell::Point vtx_point; + + for (auto& [shower_pair, mass_point] : map_shower_pair_mass_point) { + if (std::abs(mass_point.first - 135 * units::MeV + mass_offset) < mass_diff) { + if (good_showers.find(shower_pair.first) == good_showers.end() && + good_showers.find(shower_pair.second) == good_showers.end()) continue; + + shower_1 = shower_pair.first; + shower_2 = shower_pair.second; + mass_diff = std::abs(mass_point.first - 135 * units::MeV + mass_offset); + mass_save = mass_point.first; + vtx_point = mass_point.second; + } + } + + // If found good pi0, update everything + if (mass_diff < 60 * units::MeV && shower_1 && shower_2) { + pi0_showers.insert(shower_1); + pi0_showers.insert(shower_2); + + int pio_id = acc_segment_id; + acc_segment_id++; + + map_shower_pio_id[shower_1] = pio_id; + map_shower_pio_id[shower_2] = pio_id; + map_pio_id_mass[pio_id] = std::make_pair(mass_save, 2); + map_pio_id_showers[pio_id].push_back(shower_1); + map_pio_id_showers[pio_id].push_back(shower_2); + + // Update main vertex position (hack) - set to reconstructed pi0 decay point + main_vertex->fit().point = vtx_point; + main_vertex->fit().dQ = 0; + + // Add other segments from main_vertex to showers + auto [start_vtx_1, conn_type_1] = shower_1->get_start_vertex_and_type(); + if (start_vtx_1 == main_vertex && conn_type_1 == 1) { + for (auto sg : map_vertex_segments[main_vertex]) { + if (sg == shower_1->start_segment()) continue; + shower_1->add_segment(sg); + } + } + + auto [start_vtx_2, conn_type_2] = shower_2->get_start_vertex_and_type(); + if (start_vtx_2 == main_vertex && conn_type_2 == 1) { + for (auto sg : map_vertex_segments[main_vertex]) { + if (sg == shower_2->start_segment()) continue; + shower_2->add_segment(sg); + } + } + + shower_1->set_start_vertex(main_vertex, 2); + shower_1->calculate_kinematics(particle_data, recomb_model); + + shower_2->set_start_vertex(main_vertex, 2); + shower_2->calculate_kinematics(particle_data, recomb_model); + + update_shower_maps(showers, map_vertex_in_shower, map_segment_in_shower, + map_vertex_to_shower, used_shower_clusters); + + std::cout << "Pi0 (displaced vertex) found with mass: " << mass_save / units::MeV + << " MeV with " << shower_1->get_kine_charge() / units::MeV << " MeV + " + << shower_2->get_kine_charge() / units::MeV << " MeV" << std::endl; + } + } +} diff --git a/util/inc/WireCellUtil/Point.h b/util/inc/WireCellUtil/Point.h index d23340db..5fd47290 100644 --- a/util/inc/WireCellUtil/Point.h +++ b/util/inc/WireCellUtil/Point.h @@ -84,6 +84,18 @@ namespace WireCell { * projected onto the ray's direction. */ double ray_dist(const Ray& ray, const Point& point); + /** Return the perpendicular distance from a point to the infinite line + * defined by the ray. This is the shortest distance from the point to the line. */ + double ray_closest_dis(const Ray& ray, const Point& point); + + /** Return the shortest distance between two infinite lines defined by the rays. + * For skew lines, this is the distance between the two closest approach points. */ + double ray_closest_dis(const Ray& ray1, const Ray& ray2); + + /** Return a pair of points representing the closest points on two infinite lines + * defined by the rays. The first point is on ray1, the second on ray2. */ + std::pair ray_closest_points(const Ray& ray1, const Ray& ray2); + /** Return the volume of a box aligned with axes and with the ray * at opposite corners. */ double ray_volume(const Ray& ray); diff --git a/util/src/Point.cxx b/util/src/Point.cxx index 20af585a..0a81bf66 100644 --- a/util/src/Point.cxx +++ b/util/src/Point.cxx @@ -84,6 +84,92 @@ double WireCell::ray_dist(const WireCell::Ray& ray, const WireCell::Point& point return ray_unit(ray).dot(point - ray.first); } +double WireCell::ray_closest_dis(const WireCell::Ray& ray, const WireCell::Point& point) +{ + // Perpendicular distance from point to line + // Calculate vectors from point to both ray endpoints + WireCell::Vector d1 = point - ray.first; // point to tail + WireCell::Vector d2 = point - ray.second; // point to head + + // Cross product gives a vector perpendicular to the plane containing the point and line + WireCell::Vector cross = d1.cross(d2); + + // Distance = |cross product| / |direction vector| + WireCell::Vector dir = ray_vector(ray); + return cross.magnitude() / dir.magnitude(); +} + +double WireCell::ray_closest_dis(const WireCell::Ray& ray1, const WireCell::Ray& ray2) +{ + // Distance between two skew lines + // ca = vector from ray2.first to ray1.first + WireCell::Vector ca = ray1.first - ray2.first; + + // Get direction vectors + WireCell::Vector dir1 = ray_vector(ray1); + WireCell::Vector dir2 = ray_vector(ray2); + + // Cross product of direction vectors + WireCell::Vector bd = dir1.cross(dir2); + + // Distance = |ca · (dir1 × dir2)| / |dir1 × dir2| + double bd_mag = bd.magnitude(); + if (bd_mag < 1e-6) { + // Lines are parallel, use point-to-line distance + return ray_closest_dis(ray1, ray2.first); + } + + return std::abs(ca.dot(bd) / bd_mag); +} + +std::pair WireCell::ray_closest_points(const WireCell::Ray& ray1, const WireCell::Ray& ray2) +{ + // Find the closest points on two infinite lines + // Using the algorithm similar to WCP's closest_dis_points + + WireCell::Vector d = ray1.first - ray2.first; + WireCell::Vector dir1 = ray_vector(ray1); + WireCell::Vector dir2 = ray_vector(ray2); + + // Normal vector to both lines + WireCell::Vector c = dir1.cross(dir2); + double c_mag = c.magnitude(); + + if (c_mag < 1e-6) { + // Lines are parallel, return arbitrary closest points + return std::make_pair(ray1.first, ray2.first); + } + + WireCell::Vector c_unit = c.norm(); + + // Project d onto dir2 and calculate rejection + double proj_mag = d.dot(dir2) / dir2.magnitude(); + WireCell::Vector proj = proj_mag * dir2.norm(); + WireCell::Vector rej = d - proj - (d.dot(c_unit) / c_mag) * c_unit; + + // Find point on ray1 + WireCell::Vector dir1_unit = dir1.norm(); + double rej_mag = rej.magnitude(); + double scale1 = rej_mag / dir1_unit.dot(rej.norm()); + WireCell::Point tp1 = ray1.first - scale1 * dir1_unit; + + // Now find point on ray2 + WireCell::Vector d_p = d * (-1.0); + WireCell::Vector c_p = c * (-1.0); + WireCell::Vector c_p_unit = c_p.norm(); + + double proj_p_mag = d_p.dot(dir1) / dir1.magnitude(); + WireCell::Vector proj_p = proj_p_mag * dir1.norm(); + WireCell::Vector rej_p = d_p - proj_p - (d_p.dot(c_p_unit) / c_mag) * c_p_unit; + + WireCell::Vector dir2_unit = dir2.norm(); + double rej_p_mag = rej_p.magnitude(); + double scale2 = rej_p_mag / dir2_unit.dot(rej_p.norm()); + WireCell::Point tp2 = ray2.first - scale2 * dir2_unit; + + return std::make_pair(tp1, tp2); +} + double WireCell::ray_volume(const WireCell::Ray& ray) { auto diff = ray_vector(ray); From 1db83163f1a28bbbf008f68c33dda31820e1c52d Mon Sep 17 00:00:00 2001 From: Xin Qian Date: Sat, 10 Jan 2026 17:16:21 -0800 Subject: [PATCH 111/111] implement the shower_clustering_with_nv function --- clus/inc/WireCellClus/NeutrinoPatternBase.h | 3 +- clus/src/NeutrinoShowerClustering.cxx | 74 ++++++++++++++++++++- 2 files changed, 75 insertions(+), 2 deletions(-) diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h index 94a65a3d..3f2394ce 100644 --- a/clus/inc/WireCellClus/NeutrinoPatternBase.h +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -141,11 +141,12 @@ namespace WireCell::Clus::PR { void shower_clustering_with_nv_from_vertices(Graph& graph, VertexPtr main_vertex, Facade::Cluster* main_cluster, std::vector& other_clusters, std::set& showers, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); void calculate_shower_kinematics(std::set& showers, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, Graph& graph, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); void examine_merge_showers(std::set& showers, VertexPtr main_vertex, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, Graph& graph, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); - void shower_clustering_in_other_clusters(Graph& graph, VertexPtr main_vertex, std::set& showers, Facade::Cluster* main_cluster, std::vector& other_clusters, std::map map_cluster_main_vertices, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); + void shower_clustering_in_other_clusters(Graph& graph, VertexPtr main_vertex, std::set& showers, Facade::Cluster* main_cluster, std::vector& other_clusters, std::map map_cluster_main_vertices, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model, bool flag_save = true); void examine_shower_1(Graph& graph, VertexPtr main_vertex, std::set& showers, Facade::Cluster* main_cluster, std::vector& other_clusters, std::map map_cluster_main_vertices, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); void examine_showers(Graph& graph, VertexPtr main_vertex, std::set& showers, Facade::Cluster* main_cluster, std::vector& other_clusters, std::map map_cluster_main_vertices, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); void id_pi0_with_vertex(int acc_segment_id, std::set& pi0_showers, std::map& map_shower_pio_id, std::map >& map_pio_id_showers, std::map >& map_pio_id_mass, std::map >& map_pio_id_saved_pair, Graph& graph, VertexPtr main_vertex, std::set& showers, Facade::Cluster* main_cluster, std::vector& other_clusters, std::map map_cluster_main_vertices, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); void id_pi0_without_vertex(int acc_segment_id, std::set& pi0_showers, std::map& map_shower_pio_id, std::map >& map_pio_id_showers, std::map >& map_pio_id_mass, std::map >& map_pio_id_saved_pair, Graph& graph, VertexPtr main_vertex, std::set& showers, Facade::Cluster* main_cluster, std::vector& other_clusters, std::map map_cluster_main_vertices, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); + void shower_clustering_with_nv(int acc_segment_id, std::set& pi0_showers, std::map& map_shower_pio_id, std::map >& map_pio_id_showers, std::map >& map_pio_id_mass, std::map >& map_pio_id_saved_pair, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, Graph& graph, VertexPtr main_vertex, std::set& showers, Facade::Cluster* main_cluster, std::vector& other_clusters, std::map map_cluster_main_vertices, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); // deghost related functions ... diff --git a/clus/src/NeutrinoShowerClustering.cxx b/clus/src/NeutrinoShowerClustering.cxx index c5d6e1bd..d367caf1 100644 --- a/clus/src/NeutrinoShowerClustering.cxx +++ b/clus/src/NeutrinoShowerClustering.cxx @@ -1286,7 +1286,7 @@ void PatternAlgorithms::examine_merge_showers(std::set& showers, Vert } -void PatternAlgorithms::shower_clustering_in_other_clusters(Graph& graph, VertexPtr main_vertex, std::set& showers, Facade::Cluster* main_cluster, std::vector& other_clusters, std::map map_cluster_main_vertices, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model){ +void PatternAlgorithms::shower_clustering_in_other_clusters(Graph& graph, VertexPtr main_vertex, std::set& showers, Facade::Cluster* main_cluster, std::vector& other_clusters, std::map map_cluster_main_vertices, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model, bool flag_save){ if (!main_vertex || !main_cluster) return; @@ -1495,6 +1495,8 @@ void PatternAlgorithms::shower_clustering_in_other_clusters(Graph& graph, Vertex if (min_dis > 80 * units::cm) { connection_type = 4; } + + if (!flag_save) connection_type = 4; if (sg) { // Create new shower @@ -2888,3 +2890,73 @@ void PatternAlgorithms::id_pi0_without_vertex(int acc_segment_id, std::set& pi0_showers, std::map& map_shower_pio_id, std::map >& map_pio_id_showers, std::map >& map_pio_id_mass, std::map >& map_pio_id_saved_pair, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, Graph& graph, VertexPtr main_vertex, std::set& showers, Facade::Cluster* main_cluster, std::vector& other_clusters, std::map map_cluster_main_vertices, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model){ + + + + // Connect to the main cluster + shower_clustering_with_nv_in_main_cluster(graph, main_vertex, showers, + map_vertex_in_shower, map_segment_in_shower, + map_vertex_to_shower, used_shower_clusters, + vertices_in_long_muon, segments_in_long_muon); + + // Examine things connecting to the main vertex + shower_clustering_connecting_to_main_vertex(graph, main_vertex, showers, + map_vertex_in_shower, map_segment_in_shower, + map_vertex_to_shower, used_shower_clusters); + + // Shower clustering from main cluster + shower_clustering_with_nv_from_main_cluster(graph, main_vertex, main_cluster, showers, + map_vertex_in_shower, map_segment_in_shower, + map_vertex_to_shower, used_shower_clusters); + + // Shower clustering from vertices + shower_clustering_with_nv_from_vertices(graph, main_vertex, main_cluster, other_clusters, showers, + map_vertex_in_shower, map_segment_in_shower, + map_vertex_to_shower, used_shower_clusters, + vertices_in_long_muon, segments_in_long_muon, + track_fitter, dv, particle_data, recomb_model); + + // Calculate shower kinematics + calculate_shower_kinematics(showers, vertices_in_long_muon, segments_in_long_muon, + graph, track_fitter, dv, particle_data, recomb_model); + + // Examine and merge showers + examine_merge_showers(showers, main_vertex, map_vertex_in_shower, map_segment_in_shower, + map_vertex_to_shower, used_shower_clusters, + vertices_in_long_muon, segments_in_long_muon, + graph, track_fitter, dv, particle_data, recomb_model); + + // Check remaining clusters + shower_clustering_in_other_clusters(graph, main_vertex, showers, main_cluster, other_clusters, + map_cluster_main_vertices, map_vertex_in_shower, + map_segment_in_shower, map_vertex_to_shower, + used_shower_clusters, track_fitter, dv, + particle_data, recomb_model, true); + + // Calculate shower kinematics again + calculate_shower_kinematics(showers, vertices_in_long_muon, segments_in_long_muon, + graph, track_fitter, dv, particle_data, recomb_model); + + // Examine shower trunk and add to shower + examine_showers(graph, main_vertex, showers, main_cluster, other_clusters, + map_cluster_main_vertices, map_vertex_in_shower, map_segment_in_shower, + map_vertex_to_shower, used_shower_clusters, + track_fitter, dv, particle_data, recomb_model); + + // Identify pi0 with vertex + id_pi0_with_vertex(acc_segment_id, pi0_showers, map_shower_pio_id, map_pio_id_showers, map_pio_id_mass, + map_pio_id_saved_pair, graph, main_vertex, showers, main_cluster, + other_clusters, map_cluster_main_vertices, map_vertex_in_shower, + map_segment_in_shower, map_vertex_to_shower, used_shower_clusters, + track_fitter, dv, particle_data, recomb_model); + + // Identify pi0 without vertex (displaced vertex) + id_pi0_without_vertex(acc_segment_id, pi0_showers, map_shower_pio_id, map_pio_id_showers, + map_pio_id_mass, map_pio_id_saved_pair, graph, main_vertex, showers, + main_cluster, other_clusters, map_cluster_main_vertices, + map_vertex_in_shower, map_segment_in_shower, map_vertex_to_shower, + used_shower_clusters, track_fitter, dv, particle_data, recomb_model); +} \ No newline at end of file