diff --git a/aux/src/ClusterHelpers.cxx b/aux/src/ClusterHelpers.cxx index 1f69abbb..e5005de4 100644 --- a/aux/src/ClusterHelpers.cxx +++ b/aux/src/ClusterHelpers.cxx @@ -319,6 +319,8 @@ std::map Aux::count(const cluster_graph_t& cgraph, bool nod } + + std::unordered_map > Aux::blob_clusters( const cluster_graph_t& cg) { @@ -368,4 +370,4 @@ std::unordered_map > Aux::blob_clusters( } return groups; -} +} \ No newline at end of file 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/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/inc/WireCellClus/Facade_Util.h b/clus/inc/WireCellClus/Facade_Util.h index c0480ea6..872ba441 100644 --- a/clus/inc/WireCellClus/Facade_Util.h +++ b/clus/inc/WireCellClus/Facade_Util.h @@ -145,6 +145,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/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..b1f51bcd --- /dev/null +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -0,0 +1,110 @@ +#include "WireCellClus/PRGraph.h" +#include "WireCellClus/Facade_Cluster.h" +#include "WireCellClus/TrackFitting.h" + +namespace WireCell::Clus::PR { + class PatternAlgorithms{ + public: + 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); + + // 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 + 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); + + + // 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, 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 + 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); + + // 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 ); + 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); + 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); + 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); + + 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); + + // 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); + 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); + 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); + 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); + + // 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/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/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/PRGraph.h b/clus/inc/WireCellClus/PRGraph.h index cb515f04..3b9ad856 100644 --- a/clus/inc/WireCellClus/PRGraph.h +++ b/clus/inc/WireCellClus/PRGraph.h @@ -115,9 +115,27 @@ 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. + /// + /// 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); + + /// 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/inc/WireCellClus/PRSegment.h b/clus/inc/WireCellClus/PRSegment.h index 7dc0a310..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) { @@ -115,10 +117,30 @@ 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"); + // 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(); + } + + // 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; @@ -127,6 +149,10 @@ 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/inc/WireCellClus/PRSegmentFunctions.h b/clus/inc/WireCellClus/PRSegmentFunctions.h index 631cb6a7..2451c09f 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, + 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 ); @@ -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); @@ -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, @@ -88,7 +89,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/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/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/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/Facade_Util.cxx b/clus/src/Facade_Util.cxx index 169bf3c4..4553642c 100644 --- a/clus/src/Facade_Util.cxx +++ b/clus/src/Facade_Util.cxx @@ -159,6 +159,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/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/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); + } + } +} diff --git a/clus/src/NeutrinoPatternBase.cxx b/clus/src/NeutrinoPatternBase.cxx new file mode 100644 index 00000000..208dc832 --- /dev/null +++ b/clus/src/NeutrinoPatternBase.cxx @@ -0,0 +1,1276 @@ +#include "WireCellClus/NeutrinoPatternBase.h" +#include "WireCellClus/PRSegmentFunctions.h" + +using namespace WireCell::Clus::PR; +using namespace WireCell::Clus; + +std::set PatternAlgorithms::find_cluster_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_cluster_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_cluster_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_cluster_vertices(graph, cluster); + for (auto vtx : vertices_to_remove) { + if (remove_vertex(graph, vtx)) { + modified = true; + } + } + + 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; +} + +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; + // 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::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); + 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) +{ + // 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 if (main_cluster) { + // Non-main cluster: start from the point closest to main cluster + // 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"); + + 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); + } + } + } + + // 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); + + auto seg = create_segment_from_vertices(graph, cluster, v1, v2, dv); + if (!seg) { + remove_vertex(graph, v1); + remove_vertex(graph, v2); + return nullptr; + } + + // // 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); + // 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; +} + + +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); +} + +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; + } +} + +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 + // } + + // 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, 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); + + // Update the output parameters + seg = new_seg; + vtx = new_vtx; + + 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; + } + + 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; +} + + +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; +} + +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; +} + +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; +} + +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; +} + + +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); +} + +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; + } + } + } + } + + // 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(); + + // 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 + + // 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)); + 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/NeutrinoStructureExaminer.cxx b/clus/src/NeutrinoStructureExaminer.cxx new file mode 100644 index 00000000..eeed84de --- /dev/null +++ b/clus/src/NeutrinoStructureExaminer.cxx @@ -0,0 +1,3292 @@ +#include "WireCellClus/NeutrinoPatternBase.h" +#include "WireCellClus/PRSegmentFunctions.h" + +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; + + // 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; +} + +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; +} + + +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; +} + +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); + } +} + + +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; +} + +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; +} + +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; +} + + +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; +} + +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); + } +} + +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; + } + } +} + +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; +} + +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); + } +} + + + +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) { + // 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; + } + } + } + } // while continue + + if (flag_update) { + track_fitter.do_multi_tracking(true, true, true); + } + + 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 << " into " << main_vertex->ident() + << " " << main_vtx_point << std::endl; + + // Use helper function to merge vtx1 into main_vertex + merge_vertex_into_another(graph, vtx1, main_vertex, dv); + + 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; +} + 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..cecb237d --- /dev/null +++ b/clus/src/NeutrinoTrackShowerSep.cxx @@ -0,0 +1,1735 @@ +#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); + } +} + +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); + } + } +} + +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); + } + } +} + +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; + } +} + +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); + } + } + } +} + +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); + } + } +} + +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; +} + +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); + } + } + } +} + +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); +} diff --git a/clus/src/NeutrinoVertexFinder.cxx b/clus/src/NeutrinoVertexFinder.cxx new file mode 100644 index 00000000..07b064b8 --- /dev/null +++ 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; +} + diff --git a/clus/src/PRGraph.cxx b/clus/src/PRGraph.cxx index 0d858f7b..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{}; } @@ -91,6 +91,48 @@ 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; + } + + 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; + } + } diff --git a/clus/src/PRSegment.cxx b/clus/src/PRSegment.cxx index 016d809a..192d4ff5 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/PRSegmentFunctions.cxx b/clus/src/PRSegmentFunctions.cxx index 0621c502..74891c6b 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, @@ -381,7 +387,7 @@ namespace WireCell::Clus::PR { - bool break_segment(Graph& graph, SegmentPtr seg, Point point, 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()) { @@ -397,11 +403,24 @@ 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; - } + // 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); @@ -414,6 +433,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); @@ -425,27 +446,138 @@ 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); // 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); - seg1->fits(std::vector(fits.begin(), itfits+1)); - seg2->fits(std::vector(itfits, fits.end())); - vtx->fit(*itfits); + seg1->cluster(seg->cluster()); + seg2->cluster(seg->cluster()); + + // Split fits - break point included in both + if (fits.size()>0){ + 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(); - return true; + // 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 std::make_tuple(true,std::make_pair(seg1, seg2), vtx); } @@ -612,19 +744,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)); } } @@ -633,7 +779,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, @@ -815,6 +961,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); } @@ -893,9 +1040,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); } } } @@ -975,6 +1130,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; @@ -1012,8 +1171,8 @@ namespace WireCell::Clus::PR { for (size_t i = 0; i < count; 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 @@ -1033,6 +1192,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 @@ -1065,15 +1226,14 @@ 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; } // 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); @@ -1131,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; @@ -1172,8 +1332,10 @@ 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); + return std::make_tuple(false, 0, 0, 100.0); } // 4-momentum: E, px, py, pz, @@ -1195,8 +1357,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(); @@ -1243,14 +1406,14 @@ 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; 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)); @@ -1259,7 +1422,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)); @@ -1269,7 +1432,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)); @@ -1278,7 +1441,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)); @@ -1346,33 +1509,40 @@ 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); + segment->particle_score(particle_score); } - 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; } } @@ -1382,43 +1552,61 @@ 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 = 100.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); + segment->particle_score(particle_score); - 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){ @@ -1440,6 +1628,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; @@ -1571,7 +1760,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); } } } @@ -1580,7 +1769,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); } } } @@ -1723,6 +1914,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 && @@ -1901,7 +2095,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(); @@ -2036,6 +2230,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/TaggerCheckNeutrino.cxx b/clus/src/TaggerCheckNeutrino.cxx new file mode 100644 index 00000000..11275bb6 --- /dev/null +++ b/clus/src/TaggerCheckNeutrino.cxx @@ -0,0 +1,118 @@ +#include "WireCellClus/TaggerCheckNeutrino.h" +#include "WireCellClus/NeutrinoPatternBase.h" // pattern recognition ... + +class TaggerCheckNeutrino; +WIRECELL_FACTORY(TaggerCheckNeutrino, TaggerCheckNeutrino, + WireCell::IConfigurable, WireCell::Clus::IEnsembleVisitor) + +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; +}; + +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; + } + } + + // 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); + m_track_fitter.add_graph(pr_graph); + + +} + +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 << "TaggerCheckNeutrino: 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 << "TaggerCheckNeutrino: 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 << "TaggerCheckNeutrino: Set " << param_name << " = " << value << std::endl; + } catch (const std::exception& e) { + std::cerr << "TaggerCheckNeutrino: Failed to set parameter " << param_name + << ": " << e.what() << std::endl; + } + } + + std::cout << "TaggerCheckNeutrino: Successfully loaded TrackFitting configuration" << std::endl; + + } catch (const std::exception& e) { + std::cerr << "TaggerCheckNeutrino: Exception loading config: " << e.what() << std::endl; + std::cerr << "TaggerCheckNeutrino: Using default TrackFitting parameters" << std::endl; + } +} + diff --git a/clus/src/TaggerCheckSTM.cxx b/clus/src/TaggerCheckSTM.cxx index 2b13eac0..a09acb28 100644 --- a/clus/src/TaggerCheckSTM.cxx +++ b/clus/src/TaggerCheckSTM.cxx @@ -125,114 +125,367 @@ 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); + + // Create a PR::Graph with two vertices connected by this segment + // This is needed for break_segment to work properly + 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); + + // 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)}); + 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; + } + + + // // 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; + // 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 << " " << vtx_break->wcpt().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; + + } + + + m_track_fitter.add_graph(pr_graph); + m_track_fitter.do_multi_tracking(true, true, false); + + + + + // 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; + // // } + // } + + // } } diff --git a/clus/src/TrackFitting.cxx b/clus/src/TrackFitting.cxx index 5767b886..0717c774 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){ @@ -831,7 +833,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 +892,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); } @@ -916,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); @@ -1259,6 +1274,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 +1388,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) { @@ -1691,9 +1712,12 @@ 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); + // 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(); @@ -1909,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; @@ -1948,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); @@ -2067,6 +2091,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( @@ -2076,7 +2105,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) { @@ -2335,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); @@ -2734,13 +2763,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 +2782,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++) { @@ -2787,11 +2814,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); } @@ -2800,6 +2833,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); @@ -2817,34 +2851,44 @@ 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(); } } } + // 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"); + segment->set_fit_associate_vec(saved_fits, m_dv, "fit"); } // Deal with all vertices again @@ -2870,8 +2914,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); @@ -2890,6 +2941,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; } @@ -2936,7 +2989,7 @@ void TrackFitting::form_map(std::vector " << fitted_p << std::endl; + // 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()); + 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()); @@ -3461,9 +3516,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 { @@ -3530,6 +3587,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 +3595,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++) { @@ -3547,12 +3607,14 @@ 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); 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()) { @@ -4077,7 +4139,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; } @@ -4131,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); @@ -4176,6 +4238,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)); } @@ -4242,7 +4306,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; } } @@ -4270,7 +4334,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; } } @@ -4298,7 +4362,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; } } @@ -4797,7 +4861,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); @@ -4933,17 +4997,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; } @@ -5197,6 +5252,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(); @@ -5248,9 +5308,12 @@ void TrackFitting::dQ_dx_multi_fit(double dis_end_point_ext, bool flag_dQ_dx_fit case 2: map_W_charge_2D[coord_readout] = charge_coord_pair; break; } } + std::cout << "dQ/dx: " << map_U_charge_2D.size() << " " << map_V_charge_2D.size() << " " << map_W_charge_2D.size() << std::endl; // 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; @@ -5271,8 +5334,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 { @@ -5283,33 +5347,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(); @@ -5333,7 +5404,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 @@ -5349,6 +5420,8 @@ void TrackFitting::dQ_dx_multi_fit(double dis_end_point_ext, bool flag_dQ_dx_fit } else { data_u_2D(n_u) = 0; } + // std::cout << coord_key.time << " " << coord_key.channel << " " << measurement.charge << " " << measurement.charge_err << " " << data_u_2D(n_u) << std::endl; + n_u++; } @@ -5380,8 +5453,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; @@ -5390,7 +5463,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; @@ -5410,115 +5491,70 @@ 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)]; - - WireCell::Point prev_pos = fits[i-1].point; + + WireCell::Point prev_pos = (fits[i-1].point + fits[i].point) / 2.; WireCell::Point curr_pos = fits[i].point; - WireCell::Point next_pos = fits[i+1].point; - + WireCell::Point next_pos = (fits[i+1].point + fits[i].point) / 2.; + // 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()); + + // 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 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()); @@ -5526,18 +5562,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; @@ -5555,16 +5585,107 @@ 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 + // 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; + 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); @@ -5576,17 +5697,372 @@ 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; + } } - 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; + } + } + } + // std::cout << idx << " " << reg_flag_u[idx] << " " << reg_flag_v[idx] << " " << reg_flag_w[idx] << std::endl; + } } - + + + // Calculate dx for vertices (endpoints) + 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()) { + 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(); + 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 + 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 + vertex->fit().point) / 2.0); + } + } + } + } + } + } + + // 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); + + // std::cout << (extended - curr_pos).magnitude()/units::cm << " " << (connected_pts[0] - curr_pos).magnitude()/units::cm << std::endl; + } + + // 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; + + 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; + + // 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; + 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++) { + + // 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; + + auto reco_pos_raw = transform->backward(reco_pos, cluster_t0, test_wpid.face(), test_wpid.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()); + 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(); + + // 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; + + 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); + } + } + + // 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) { + // 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); + + 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; + // 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; + 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[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; + } + } + } + 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[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; + } + } + } + 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; + } + } + } + + 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; + } + } + } + + 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; + } + } + } + + // std::cout << vertex_idx << " " << reg_flag_u[vertex_idx] << " " << reg_flag_v[vertex_idx] << " " << reg_flag_w[vertex_idx] << std::endl; + } + + // 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) { @@ -5646,13 +6122,17 @@ 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); - 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; const size_t n3d = static_cast(n_3D_pos); for (size_t i = 0; i < n3d; i++) { @@ -5670,24 +6150,23 @@ 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] > 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); - 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; } } // 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; @@ -5712,7 +6191,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); + + // 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) { + 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()); + } + for (Eigen::SparseMatrix::InnerIterator it(RV, k); it; ++it) { + 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()); + } + for (Eigen::SparseMatrix::InnerIterator it(RW, k); it; ++it) { + 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]))); + } + + // Update vertex and segment fit results for (const auto& [vertex, vertex_idx] : vertex_index_map) { @@ -5915,8 +6415,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; @@ -5932,7 +6434,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; @@ -5943,10 +6445,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(); @@ -5995,10 +6501,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; @@ -6012,7 +6526,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; @@ -6022,9 +6536,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 @@ -6037,7 +6554,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()); @@ -6045,8 +6562,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); @@ -6067,7 +6585,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()); @@ -6075,7 +6593,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); @@ -6448,6 +6968,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; @@ -6459,6 +6980,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); } @@ -6474,16 +6997,182 @@ 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) { + // 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; + // 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; + // 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); + + // 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; + // } + // } + } @@ -6492,16 +7181,155 @@ 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; + // 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; + // } + // } + // 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; + // 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); + // 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) { + // 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) { + // // 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; + // } + // } + // 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; + // 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; + // } + // } + } @@ -6523,7 +7351,29 @@ void TrackFitting::do_multi_tracking(bool flag_dQ_dx_fit_reg, bool flag_dQ_dx_fi 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); + + + 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; + // } + // } + } } @@ -6763,7 +7613,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); @@ -6846,6 +7696,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 diff --git a/clus/src/connect_graph.cxx b/clus/src/connect_graph.cxx index 41728513..8d90abac 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 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) { + // 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(std::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_abs); + + 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_graph_relaxed.cxx b/clus/src/connect_graph_relaxed.cxx index cebb39b4..a76c26b6 100644 --- a/clus/src/connect_graph_relaxed.cxx +++ b/clus/src/connect_graph_relaxed.cxx @@ -602,3 +602,511 @@ void Graphs::connect_graph_relaxed( } + 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(); + + // 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= 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(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(); + 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, pc1, pc1_global_index); + dir1 = dir1 * -1; + + 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()); + + // 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; + } + +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 e74a3faf..6a04d8f1 100644 --- a/clus/src/connect_graphs.h +++ b/clus/src/connect_graphs.h @@ -44,7 +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::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 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 diff --git a/gen/src/RecombinationModels.cxx b/gen/src/RecombinationModels.cxx index 233150c4..5da6135f 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; @@ -51,13 +51,14 @@ 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; } 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)