diff --git a/aux/src/ClusterHelpers.cxx b/aux/src/ClusterHelpers.cxx index 1f69abbb7..e5005de41 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 b466c51fc..32cbe49e5 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/DynamicPointCloud.h b/clus/inc/WireCellClus/DynamicPointCloud.h index 72d55e8d7..8fd6fdd4e 100644 --- a/clus/inc/WireCellClus/DynamicPointCloud.h +++ b/clus/inc/WireCellClus/DynamicPointCloud.h @@ -56,6 +56,8 @@ namespace WireCell::Clus::Facade { const std::unordered_map &kd2d_l2g(const int plane, const int face, const int apa) const; const std::unordered_map> &kd2d_g2l(const int plane, const int face, const int apa) const; + geo_point_t get_center_point_radius(const geo_point_t &p_test, const double radius) const; + /// @brief: kd2d().radius(radius) /// @return: [dist, Cluster, global point_index] std::vector> get_2d_points_info(const geo_point_t &p, @@ -66,6 +68,7 @@ namespace WireCell::Clus::Facade { /// @brief: dist, Cluster, global point_index std::tuple get_closest_2d_point_info(const geo_point_t &p, const int plane, const int face, const int apa) const; + std::pair hough_transform(const geo_point_t &origin, const double dis) const; geo_point_t vhough_transform(const geo_point_t &origin, const double dis) const; @@ -89,6 +92,10 @@ namespace WireCell::Clus::Facade { make_points_cluster(const Cluster *cluster, const std::map> &wpid_params, bool flag_wrap = false); + std::vector + make_points_cluster_steiner(const Cluster *cluster, + const std::map> &wpid_params, bool flag_wrap = false); + std::vector make_points_cluster_skeleton( const Cluster *cluster, const IDetectorVolumes::pointer dv, const std::map> &wpid_params, diff --git a/clus/inc/WireCellClus/Facade_Cluster.h b/clus/inc/WireCellClus/Facade_Cluster.h index 741396e01..05c5ebf63 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 d819d489d..2f01a4ac7 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_Grouping.h b/clus/inc/WireCellClus/Facade_Grouping.h index 45d335baf..e5c98f77c 100644 --- a/clus/inc/WireCellClus/Facade_Grouping.h +++ b/clus/inc/WireCellClus/Facade_Grouping.h @@ -18,11 +18,15 @@ #include "WireCellClus/Facade_Mixins.h" #include "WireCellClus/Facade_Util.h" +// Include PRGraphType for PR::Graph (it's a type alias, not a class, so can't forward declare) +#include "WireCellClus/PRGraphType.h" + // forward declare namespace WireCell::Clus { class FiducialUtils; using FiducialUtilsPtr = std::shared_ptr; + class TrackFitting; } namespace WireCell::Clus::Facade { @@ -241,8 +245,20 @@ namespace WireCell::Clus::Facade { // FiducialUtils. FiducialUtilsPtr get_fiducialutils() const { return m_fiducialutils; } void set_fiducialutils(FiducialUtilsPtr fd) { m_fiducialutils = fd; } + + // TrackFitting storage for track fitting data + // Visitors like TaggerCheckNeutrino can store their TrackFitting here + // for access by other components (e.g., bee output in MABC, UbooneMagnifyTrackingSink) + // The PRGraph is accessible via get_track_fitting()->get_graph() + std::shared_ptr get_track_fitting() const { return m_track_fitting; } + void set_track_fitting(std::shared_ptr tf) { m_track_fitting = tf; } + + // Convenience accessor for PRGraph (delegates to TrackFitting) + std::shared_ptr get_pr_graph() const; + private: FiducialUtilsPtr m_fiducialutils; + std::shared_ptr m_track_fitting; // Build cache for a specific APA/face/plane void build_wire_cache(int apa, int face, int plane) const; diff --git a/clus/inc/WireCellClus/Facade_Util.h b/clus/inc/WireCellClus/Facade_Util.h index c0480ea60..872ba4412 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/MultiAlgBlobClustering.h b/clus/inc/WireCellClus/MultiAlgBlobClustering.h index d9ffdf252..fb130500a 100644 --- a/clus/inc/WireCellClus/MultiAlgBlobClustering.h +++ b/clus/inc/WireCellClus/MultiAlgBlobClustering.h @@ -87,9 +87,10 @@ namespace WireCell::Clus { // New helper function to fill bee points void fill_bee_points(const std::string& name, const Facade::Grouping& grouping); void fill_bee_points_from_cluster( - Bee::Points& bpts, const Facade::Cluster& cluster, + Bee::Points& bpts, const Facade::Cluster& cluster, const std::string& pcname, const std::vector& coords, int filter); + void fill_bee_points_from_pr_graph(const std::string& name, const Facade::Grouping& grouping); void fill_bee_patches_from_grouping(const Facade::Grouping& grouping); void fill_bee_patches_from_cluster(const Facade::Cluster& cluster); diff --git a/clus/inc/WireCellClus/MyFCN.h b/clus/inc/WireCellClus/MyFCN.h new file mode 100644 index 000000000..c45479b35 --- /dev/null +++ b/clus/inc/WireCellClus/MyFCN.h @@ -0,0 +1,112 @@ +#ifndef WIRECELLCLUS_MYFCN_H +#define WIRECELLCLUS_MYFCN_H + +#include "WireCellClus/PRGraph.h" +#include "WireCellClus/Facade_Cluster.h" +#include "WireCellUtil/Units.h" +#include "WireCellClus/TrackFitting.h" + + +#include +#include + +namespace WireCell::Clus::PR { + + /// MyFCN: Vertex fitting class for pattern recognition + /// Performs vertex position fitting by minimizing distances from segments + class MyFCN { + public: + /// Constructor + /// @param vtx The vertex to fit + /// @param flag_vtx_constraint Whether to constrain the vertex position + /// @param vtx_constraint_range Range for vertex constraint + /// @param vertex_protect_dis Protection distance for vertex + /// @param vertex_protect_dis_short_track Protection distance for short tracks + /// @param fit_dis Fitting distance + MyFCN(VertexPtr vtx, + bool flag_vtx_constraint = false, + double vtx_constraint_range = 1*units::cm, + double vertex_protect_dis = 1.5*units::cm, + double vertex_protect_dis_short_track = 0.9*units::cm, + double fit_dis = 6*units::cm); + + ~MyFCN(); + + /// Update the fitting range parameters + void update_fit_range(double tmp_vertex_protect_dis = 1.5*units::cm, + double tmp_vertex_protect_dis_short_track = 0.9*units::cm, + double tmp_fit_dis = 6*units::cm); + + /// Add a segment to the fitting + void AddSegment(SegmentPtr sg); + + /// Fit the vertex position + /// @return pair of (success flag, fitted position) + std::pair FitVertex(); + + /// Update information after fitting + /// @param fit_pos The fitted position + /// @param temp_cluster The cluster being processed + /// @param default_dis_cut Default distance cut + void UpdateInfo(Facade::geo_point_t fit_pos, + Facade::Cluster& temp_cluster, + TrackFitting& track_fitter, IDetectorVolumes::pointer dv, + double default_dis_cut = 4.0*units::cm); + + /// Get segment information at index i + /// @return pair of (segment pointer, index) + std::pair get_seg_info(int i); + + /// Get number of fittable tracks + int get_fittable_tracks(); + + /// Get vertex constraint flag + bool get_flag_vtx_constraint() { return flag_vtx_constraint; } + + /// Set vertex constraint flag + void set_flag_vtx_constraint(bool val) { flag_vtx_constraint = val; } + + /// Set vertex constraint range + void set_vtx_constraint_range(double val) { vtx_constraint_range = val; } + + /// Get the vector of segments + std::vector& get_segments() { return segments; } + + /// Get the vector of point vectors + std::vector>& get_vec_points() { return vec_points; } + + /// Print points for debugging + void print_points(); + + /// Set enforce two track fit flag + void set_enforce_two_track_fit(bool val) { enforce_two_track_fit = val; } + + /// Get enforce two track fit flag + bool get_enforce_two_track_fit() { return enforce_two_track_fit; } + + private: + VertexPtr vtx; + bool enforce_two_track_fit; + bool flag_vtx_constraint; + double vtx_constraint_range; + + double vertex_protect_dis; + double vertex_protect_dis_short_track; + double fit_dis; + + std::vector segments; + std::vector> vec_points; + + // PCA directions: tuple of (dir1, dir2, dir3) + std::vector> vec_PCA_dirs; + + // PCA eigenvalues: tuple of (val1, val2, val3) + std::vector> vec_PCA_vals; + + // Centers for each segment + std::vector vec_centers; + }; + +} // namespace WireCell::Clus::PR + +#endif // WIRECELLCLUS_MYFCN_H diff --git a/clus/inc/WireCellClus/NeutrinoPatternBase.h b/clus/inc/WireCellClus/NeutrinoPatternBase.h new file mode 100644 index 000000000..3f2394ce4 --- /dev/null +++ b/clus/inc/WireCellClus/NeutrinoPatternBase.h @@ -0,0 +1,165 @@ +#include "WireCellClus/PRGraph.h" +#include "WireCellClus/Facade_Cluster.h" +#include "WireCellClus/TrackFitting.h" +#include "WireCellClus/PRShower.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); + + // PCA calculation + std::pair calc_PCA_main_axis(std::vector& points); + + // vertex related functions + bool search_for_vertex_activities(Graph& graph, VertexPtr vertex, std::set& segments_set, Facade::Cluster& cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, double search_range = 1.5*units::cm); + bool eliminate_short_vertex_activities(Graph& graph, Facade::Cluster& cluster, VertexPtr main_vertex, std::set& existing_segments, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); + std::tuple examine_main_vertex_candidate(Graph& graph, VertexPtr vertex); + VertexPtr compare_main_vertices_all_showers(Graph& graph, Facade::Cluster& cluster, std::vector& vertex_candidates, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); + float calc_conflict_maps(Graph& graph, VertexPtr vertex); + VertexPtr compare_main_vertices(Graph& graph, Facade::Cluster& cluster, std::vector& vertex_candidates); + std::pair find_cont_muon_segment(Graph &graph, SegmentPtr sg, VertexPtr vtx, bool flag_ignore_dQ_dx = false); + bool examine_direction(Graph& graph, VertexPtr vertex, VertexPtr main_vertex, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model, bool flag_final = false); + + bool fit_vertex(Facade::Cluster& cluster, VertexPtr vertex, VertexPtr main_vertex, std::set& sg_set, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); + void improve_vertex(Graph& graph, Facade::Cluster& cluster, VertexPtr main_vertex, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model, bool flag_search_vertex_activity = true , bool flag_final_vertex = false); + void determine_main_vertex(Graph& graph, Facade::Cluster& cluster, VertexPtr main_vertex, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model, bool flag_print = true); + void change_daughter_type(Graph& graph, VertexPtr vertex, SegmentPtr segment, int particle_type, double mass, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); + void examine_main_vertices_local(Graph& graph, std::vector& vertices, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); + + // cluster functions ... + Facade::geo_vector_t calc_dir_cluster(Graph& graph, Facade::Cluster& cluster, const Facade::geo_point_t& orig_p, double dis_cut); + Facade::Cluster* swap_main_cluster(Facade::Cluster& new_main_cluster, Facade::Cluster& old_main_cluster, std::vector& other_clusters); + void examine_main_vertices(Graph& graph, std::map map_cluster_main_vertices, Facade::Cluster* main_cluster, std::vector& other_clusters); + + VertexPtr compare_main_vertices_global(Graph& graph, std::vector& vertex_candidates, Facade::Cluster& main_cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); + Facade::Cluster* check_switch_main_cluster(Graph& graph, std::map map_cluster_main_vertices, Facade::Cluster* main_cluster, std::vector& other_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); + Facade::Cluster* check_switch_main_cluster_2(Graph& graph, VertexPtr temp_main_vertex, Facade::Cluster* max_length_cluster, Facade::Cluster* main_cluster, std::vector& other_clusters); + VertexPtr determine_overall_main_vertex(Graph& graph, std::map map_cluster_main_vertices, Facade::Cluster* main_cluster, std::vector& other_clusters, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model ); + + // global information transfer + void transfer_info_from_segment_to_cluster(Graph& graph, Facade::Cluster& cluster, const std::string& cloud_name = "associated_points"); + + // print information + void print_segs_info(Graph& graph, Facade::Cluster& cluster, VertexPtr vertex= nullptr); + + // shower related functions + void update_shower_maps(std::set& showers, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters); + void shower_clustering_with_nv_in_main_cluster(Graph& graph, VertexPtr main_vertex, std::set& showers, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, std::set& vertices_in_long_muon, std::set& segments_in_long_muon); + void shower_clustering_connecting_to_main_vertex(Graph& graph, VertexPtr main_vertex, std::set& showers, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters); + void shower_clustering_with_nv_from_main_cluster(Graph& graph, VertexPtr main_vertex, Facade::Cluster* main_cluster, std::set& showers, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters); + void shower_clustering_with_nv_from_vertices(Graph& graph, VertexPtr main_vertex, Facade::Cluster* main_cluster, std::vector& other_clusters, std::set& showers, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); + void calculate_shower_kinematics(std::set& showers, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, Graph& graph, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); + void examine_merge_showers(std::set& showers, VertexPtr main_vertex, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, Graph& graph, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); + void shower_clustering_in_other_clusters(Graph& graph, VertexPtr main_vertex, std::set& showers, Facade::Cluster* main_cluster, std::vector& other_clusters, std::map map_cluster_main_vertices, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model, bool flag_save = true); + void examine_shower_1(Graph& graph, VertexPtr main_vertex, std::set& showers, Facade::Cluster* main_cluster, std::vector& other_clusters, std::map map_cluster_main_vertices, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); + void examine_showers(Graph& graph, VertexPtr main_vertex, std::set& showers, Facade::Cluster* main_cluster, std::vector& other_clusters, std::map map_cluster_main_vertices, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); + void id_pi0_with_vertex(int acc_segment_id, std::set& pi0_showers, std::map& map_shower_pio_id, std::map >& map_pio_id_showers, std::map >& map_pio_id_mass, std::map >& map_pio_id_saved_pair, Graph& graph, VertexPtr main_vertex, std::set& showers, Facade::Cluster* main_cluster, std::vector& other_clusters, std::map map_cluster_main_vertices, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); + void id_pi0_without_vertex(int acc_segment_id, std::set& pi0_showers, std::map& map_shower_pio_id, std::map >& map_pio_id_showers, std::map >& map_pio_id_mass, std::map >& map_pio_id_saved_pair, Graph& graph, VertexPtr main_vertex, std::set& showers, Facade::Cluster* main_cluster, std::vector& other_clusters, std::map map_cluster_main_vertices, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); + void shower_clustering_with_nv(int acc_segment_id, std::set& pi0_showers, std::map& map_shower_pio_id, std::map >& map_pio_id_showers, std::map >& map_pio_id_mass, std::map >& map_pio_id_saved_pair, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, Graph& graph, VertexPtr main_vertex, std::set& showers, Facade::Cluster* main_cluster, std::vector& other_clusters, std::map map_cluster_main_vertices, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); + + + // deghost related functions ... + void order_clusters(Graph& graph, std::vector& ordered_clusters, std::map >& map_cluster_to_segments, std::map& map_cluster_total_length); + void deghost_clusters(Graph& graph, std::vector& all_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); + void order_segments(std::vector& ordered_segments, std::vector& segments); + void deghost_segments(Graph& graph, std::map map_cluster_main_vertices, std::vector& all_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); + void deghosting(Graph& graph, std::map map_cluster_main_vertices, std::vector& all_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv ); + + // energy calculation ... + double cal_corr_factor(WireCell::Point& pt, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); + double cal_kine_charge(ShowerPtr Shower, Graph& graph, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); + double cal_kine_charge(SegmentPtr segment, Graph& graph, TrackFitting& track_fitter, IDetectorVolumes::pointer dv); + + }; +} diff --git a/clus/inc/WireCellClus/NeutrinoTaggerCosmic.h b/clus/inc/WireCellClus/NeutrinoTaggerCosmic.h new file mode 100644 index 000000000..e69de29bb diff --git a/clus/inc/WireCellClus/NeutrinoTaggerNuE.h b/clus/inc/WireCellClus/NeutrinoTaggerNuE.h new file mode 100644 index 000000000..e69de29bb diff --git a/clus/inc/WireCellClus/NeutrinoTaggerNuMu.h b/clus/inc/WireCellClus/NeutrinoTaggerNuMu.h new file mode 100644 index 000000000..e69de29bb diff --git a/clus/inc/WireCellClus/NeutrinoTaggerPi0.h b/clus/inc/WireCellClus/NeutrinoTaggerPi0.h new file mode 100644 index 000000000..e69de29bb diff --git a/clus/inc/WireCellClus/NeutrinoTaggerSSM.h b/clus/inc/WireCellClus/NeutrinoTaggerSSM.h new file mode 100644 index 000000000..e69de29bb diff --git a/clus/inc/WireCellClus/NeutrinoTaggerSinglePhoton.h b/clus/inc/WireCellClus/NeutrinoTaggerSinglePhoton.h new file mode 100644 index 000000000..e69de29bb diff --git a/clus/inc/WireCellClus/PRCommon.h b/clus/inc/WireCellClus/PRCommon.h index 48769bbe8..8b19b9638 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; @@ -143,7 +143,7 @@ namespace WireCell::Clus::PR { /** Return true if fit information has been filled */ bool valid() const { - if (index < 0 || range < 0) return false; + if (index < 0 ) return false; return true; } }; diff --git a/clus/inc/WireCellClus/PRGraph.h b/clus/inc/WireCellClus/PRGraph.h index cb515f04a..3b9ad8561 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 7dc0a3104..9d944b8d7 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) { @@ -110,15 +112,40 @@ namespace WireCell::Clus::PR { // reset fit properties ... void reset_fit_prop(); + void clear_fit(const IDetectorVolumes::pointer& dv, const std::string& cloud_name="fit"); + int fit_index(int i); void fit_index(int i, int idx); bool fit_flag_skip(int i); 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(); + } + + bool reset_global_indices(); + bool reset_global_indices(const std::string& cloud_name); + + // Add public accessor: + int id() const { return m_id; } + void set_id(int id) { m_id = id; } private: + // Add to PRSegment.h private section: + int m_id{-1}; std::vector m_wcpts; std::vector m_fits; @@ -127,6 +154,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 631cb6a73..2451c09f7 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/PRShower.h b/clus/inc/WireCellClus/PRShower.h index edaa9f524..8a274ac0b 100644 --- a/clus/inc/WireCellClus/PRShower.h +++ b/clus/inc/WireCellClus/PRShower.h @@ -7,6 +7,9 @@ #include "WireCellUtil/Flagged.h" #include "WireCellUtil/Point.h" +#include "WireCellIface/IRecombinationModel.h" +#include "WireCellClus/ParticleDataSet.h" + namespace WireCell::Clus::PR { /** The "flags" that may be set on a shower. @@ -62,6 +65,7 @@ namespace WireCell::Clus::PR { public: Shower(Graph& graph); + virtual ~Shower(); // The bag of attributes is directly exposed to user. @@ -96,21 +100,96 @@ namespace WireCell::Clus::PR { the underlying graph, this function is a no-op and the stored start_vertex is nullified. */ - Shower& start_vertex(VertexPtr vtx); + Shower& set_start_vertex(VertexPtr vtx, int type); + std::pair get_start_vertex_and_type() { + return std::make_pair(m_start_vertex, data.start_connection_type); + } /** Chainable setter of start segment. This has the same semantics and caveats as the chainable setter: `start_vertex(VertexPtr)`. */ - Shower& start_segment(SegmentPtr seg); + Shower& set_start_segment(SegmentPtr seg, bool flag_include_vertices = false, const std::string& cloud_name_fit = "fit", const std::string& cloud_name_associate = "associate_points"); + + Shower& set_start_point( WireCell::Point pt ) { + data.start_point = pt; + return *this; + } + WireCell::Point get_start_point() const { + return data.start_point; + } + WireCell::Point get_end_point() const { + return data.end_point; + } + + void add_segment(SegmentPtr seg, bool flag_include_vertices = false, const std::string& cloud_name_fit = "fit", const std::string& cloud_name_associate = "associate_points"); + + // particle type + int get_particle_type(){return data.particle_type;}; + void set_particle_type(int val){data.particle_type = val;}; + + // access flags + void set_flag_kinematics(bool val); + bool get_flag_kinematics(); + bool get_flag_shower(); + + // return kinematic energy estimation + double get_kine_range(){return data.kenergy_range;}; + double get_kine_dQdx(){return data.kenergy_dQdx;}; + void set_kine_charge(double val){data.kenergy_charge = val;}; + double get_kine_charge(){return data.kenergy_charge;}; + double get_kine_best(){return data.kenergy_best;}; + + // return initial direction ... + WireCell::Vector& get_init_dir(){return data.init_dir;}; + + // Get point cloud by name (convenience wrapper around dpcloud) + std::shared_ptr get_pcloud(const std::string& cloud_name = "fit") { + return this->dpcloud(cloud_name); + } + std::shared_ptr get_pcloud(const std::string& cloud_name = "associate_points") const { + return this->dpcloud(cloud_name); + } + + // Add all segments and vertices from another shower to this one + void add_shower(Shower& temp_shower, const std::string& cloud_name_fit = "fit", const std::string& cloud_name_associate = "associate_points"); + void complete_structure_with_start_segment(std::set& used_segments, const std::string& cloud_name_fit = "fit", const std::string& cloud_name_associate = "associate_points"); + + + // get the information from the shower + void fill_sets(std::set& used_vertices, std::set& used_segments, bool flag_exclude_start_segment = true); + void fill_point_vector(std::vector& points, bool flag_main = true); + TrajectoryView& fill_maps(); + + std::pair, std::set> get_connected_pieces(SegmentPtr seg); + + std::pair get_last_segment_vertex_long_muon(std::set& segments_in_muons); + + // some simple get functions + int get_num_main_segments(); + int get_num_segments(); + + double get_total_length(); + double get_total_length(Facade::Cluster* cluster); + double get_total_track_length(); + + std::vector get_stem_dQ_dx(VertexPtr vertex, SegmentPtr segment, int limit = 20); + + // calculate the kinematics + void update_particle_type(const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); + void calculate_kinematics(const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); + void calculate_kinematics_long_muon(std::set& segments_in_muons, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model); private: + Graph& m_full_graph; VertexPtr m_start_vertex; SegmentPtr m_start_segment; }; + using ShowerPtr = std::shared_ptr; + } #endif diff --git a/clus/inc/WireCellClus/PRShowerFunctions.h b/clus/inc/WireCellClus/PRShowerFunctions.h index ea5a4432c..859883425 100644 --- a/clus/inc/WireCellClus/PRShowerFunctions.h +++ b/clus/inc/WireCellClus/PRShowerFunctions.h @@ -1,6 +1,10 @@ #ifndef WIRECELL_CLUS_PR_SHOWER_FUNCTIONS #define WIRECELL_CLUS_PR_SHOWER_FUNCTIONS +#include "WireCellClus/PRShower.h" +#include "WireCellUtil/Units.h" + + namespace WireCell::Clus::PR { /** Modify shower assuming shower kinematics. @@ -8,11 +12,15 @@ namespace WireCell::Clus::PR { * This free function is is equivalent to the method of WCP's * WCShower::calculate_kinematics(). */ - void shower_kinematics(ShowerPtr shower); - void update_particle_type(ShowerPtr shower); - - void longmuon_kinematics(ShowerPtr shower); + std::pair shower_get_closest_point(Shower& shower, const WireCell::Point& point, const std::string& cloud_name = "fit"); + + double shower_get_closest_dis(Shower& shower, SegmentPtr seg, const std::string& cloud_name = "fit"); + + double shower_get_dis(Shower& shower, SegmentPtr seg, const std::string& cloud_name = "fit"); + + WireCell::Vector shower_cal_dir_3vector(Shower& shower, const WireCell::Point& p, double dis_cut = 15*units::cm); + } #endif diff --git a/clus/inc/WireCellClus/PRTrajectoryView.h b/clus/inc/WireCellClus/PRTrajectoryView.h index 5d337eda9..b51d9ada1 100644 --- a/clus/inc/WireCellClus/PRTrajectoryView.h +++ b/clus/inc/WireCellClus/PRTrajectoryView.h @@ -104,6 +104,12 @@ namespace WireCell::Clus::PR { */ bool remove_segment(SegmentPtr seg); + /** Get the set of node descriptors in this view. */ + const node_unordered_set& nodes() const { return m_nodes; } + + /** Get the set of edge descriptors in this view. */ + const edge_unordered_set& edges() const { return m_edges; } + private: view_graph_type m_graph; node_unordered_set m_nodes; diff --git a/clus/inc/WireCellClus/PatternDebugIO.h b/clus/inc/WireCellClus/PatternDebugIO.h new file mode 100644 index 000000000..ba36c01ca --- /dev/null +++ b/clus/inc/WireCellClus/PatternDebugIO.h @@ -0,0 +1,42 @@ +#ifndef WIRECELLCLUS_PATTERNDEBUG_IO +#define WIRECELLCLUS_PATTERNDEBUG_IO + +#include "WireCellClus/Facade.h" +#include "WireCellClus/TrackFitting.h" +#include "WireCellUtil/PointTree.h" + +#include +#include + +namespace WireCell::Clus::PR::DebugIO { + + /// Dump the inputs to init_first_segment() as a JSON file. + /// Call this from TaggerCheckNeutrino::visit() before init_first_segment. + void dump_init_first_segment_inputs( + const std::string& output_path, + const Facade::Cluster& cluster, + const Facade::Cluster* main_cluster, + bool flag_back_search, + const TrackFitting& track_fitter); + + /// Data reconstructed from a dump file for test replay. + struct LoadedTestData { + // Owns the tree; must outlive cluster/main_cluster pointers. + std::unique_ptr grouping_node; + + Facade::Cluster* cluster{nullptr}; + Facade::Cluster* main_cluster{nullptr}; // may equal cluster + bool flag_back_search{true}; + TrackFitting::Parameters trackfitting_params; + + // Precomputed boundary steiner graph indices (avoids needing anode) + std::pair boundary_steiner_indices{0, 0}; + }; + + /// Load a dump file and reconstruct minimal Facade::Cluster objects + /// suitable for calling init_first_segment(). + LoadedTestData load_init_first_segment_inputs(const std::string& input_path); + +} + +#endif diff --git a/clus/inc/WireCellClus/TaggerCheckNeutrino.h b/clus/inc/WireCellClus/TaggerCheckNeutrino.h new file mode 100644 index 000000000..b0df59393 --- /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 = std::make_shared(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 std::shared_ptr 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 8c1b6a932..e6d8f4667 100644 --- a/clus/inc/WireCellClus/TrackFitting.h +++ b/clus/inc/WireCellClus/TrackFitting.h @@ -151,7 +151,7 @@ namespace WireCell::Clus { std::shared_ptr get_graph() const { return m_graph; } void clear_graph(); - + void add_cluster(std::shared_ptr cluster); // collect charge void prepare_data(); @@ -281,12 +281,31 @@ namespace WireCell::Clus { struct ChargeMeasurement { double charge, charge_err; int flag; - - ChargeMeasurement(double q = 0.0, double qe = 0.0, int f = 0) + + ChargeMeasurement(double q = 0.0, double qe = 0.0, int f = 0) : charge(q), charge_err(qe), flag(f) {} }; + /// Fitted 2D charge result: measured + predicted + cluster association + struct FittedCharge2D { + double charge; // original measurement charge + double charge_err; // original measurement uncertainty + double pred_charge; // predicted charge (un-whitened, same units as charge) + int flag; // 0=dead, 1=live, 2=bad + std::set clusters; + }; + + using WireTime = std::pair; // (wire_index, time_slice) + using APAFacePlane = std::tuple; // (apa, face, plane); + // Fill fitted 2D charge results after dQ/dx fitting + void fill_fitted_charge_2d( + const std::map>>& map_U, + const std::map>>& map_V, + const std::map>>& map_W, + const Eigen::VectorXd& pred_u, const Eigen::VectorXd& pred_v, const Eigen::VectorXd& pred_w, + double rel_uncer_ind, double rel_uncer_col, + double add_uncer_ind, double add_uncer_col); // point associations void form_point_association(std::shared_ptr segment, WireCell::Point &p, PlaneData& temp_2dut, PlaneData& temp_2dvt, PlaneData& temp_2dwt, double dis_cut, int nlevel, double time_tick_cut ); @@ -331,7 +350,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); @@ -357,6 +376,12 @@ namespace WireCell::Clus { */ std::map get_all_anodes() const; + /** + * Get the grouping associated with this TrackFitting instance + * @return Pointer to Grouping, or nullptr if not set + */ + Facade::Grouping* grouping() const { return m_grouping; } + /** * Get channel number for a specific wire location * Uses hybrid caching for optimal performance @@ -376,6 +401,8 @@ namespace WireCell::Clus { */ std::vector> get_wires_for_channel(int apa, int channel_number) const; + // map_apa_ch_plane_wires: (apa,channel) -> vector of (face, plane, wire) + void collect_2D_charge(std::map& charge_2d_u, std::map& charge_2d_v, std::map& charge_2d_w, std::map, std::vector>>& map_apa_ch_plane_wires); /** * Clear all caches (useful for memory management) */ @@ -430,6 +457,24 @@ namespace WireCell::Clus { std::vector> get_paf() const {return paf;} std::vector get_reduced_chi2() const { return reduced_chi2; } + // Measured 2D charge data access + const std::map& get_charge_data() const { return m_charge_data; } + + // Fitted 2D charge data organized by (apa, face, plane) -> (wire, time) + const std::map>& get_fitted_charge_2d() const { return m_fitted_charge_2d; } + + /** + * Get geometry information for wire plane offsets + * @return Map of WirePlaneId to tuple (offset_t, offset_u, offset_v, offset_w) + */ + const std::map>& get_wpid_offsets() const { return wpid_offsets; } + + /** + * Get geometry information for wire plane slopes + * @return Map of WirePlaneId to tuple (slope_t, slope_yu_zu, slope_yv_zv, slope_yw_zw) + */ + const std::map, std::pair, std::pair>>& get_wpid_slopes() const { return wpid_slopes; } + private: // Core parameters - centralized storage Parameters m_params; @@ -495,6 +540,9 @@ namespace WireCell::Clus { // Global (apa, time, channel) to blobs std::map > global_rb_map; + // Fitted 2D charge organized by (apa, face, plane) -> (wire, time) + std::map> m_fitted_charge_2d; + // global geometry void BuildGeometry(); diff --git a/clus/src/DynamicPointCloud.cxx b/clus/src/DynamicPointCloud.cxx index 838b49a02..27889647b 100644 --- a/clus/src/DynamicPointCloud.cxx +++ b/clus/src/DynamicPointCloud.cxx @@ -186,6 +186,32 @@ void DynamicPointCloud::add_points(const std::vector &points) { } } +geo_point_t DynamicPointCloud::get_center_point_radius(const geo_point_t &p_test, const double radius) const{ + auto &kd3d = this->kd3d(); + + // Create query point + std::vector query = {p_test.x(), p_test.y(), p_test.z()}; + + // Perform radius search (NFKDVec uses squared distance) + auto results = kd3d.radius(radius * radius, query); + + // Calculate center point + geo_point_t center(0, 0, 0); + int ncount = 0; + + for (const auto &[idx, _] : results) { + const auto &pt = m_points[idx]; + center.set(center.x() + pt.x, center.y() + pt.y, center.z() + pt.z); + ncount++; + } + + if (ncount > 0) { + center.set(center.x() / ncount, center.y() / ncount, center.z() / ncount); + } + + return center; +} + @@ -452,6 +478,83 @@ std::vector Clus::Facade::make_points_cluster( return dpc_points; } +std::vector Clus::Facade::make_points_cluster_steiner(const Cluster *cluster, const std::map> &wpid_params, bool flag_wrap){ + if (!cluster) { + SPDLOG_WARN("make_points_cluster_steiner: null cluster return empty points"); + return {}; + } + + // Check if steiner point cloud exists + if (!cluster->has_pc("steiner_pc")) { + SPDLOG_WARN("make_points_cluster_steiner: cluster has no steiner_pc"); + return {}; + } + + const auto& steiner_pc = cluster->get_pc("steiner_pc"); + const auto& coords = cluster->get_default_scope().coords; + const auto& x_coords = steiner_pc.get(coords.at(0))->elements(); + const auto& y_coords = steiner_pc.get(coords.at(1))->elements(); + const auto& z_coords = steiner_pc.get(coords.at(2))->elements(); + const auto& wpid_array = steiner_pc.get("wpid")->elements(); + + const size_t num_points = x_coords.size(); + std::vector dpc_points; + dpc_points.reserve(num_points); + + // Cache commonly referenced WPIDs and their params to avoid map lookups + std::unordered_map> cached_params; + + for (size_t ipt = 0; ipt < num_points; ++ipt) { + geo_point_t pt(x_coords[ipt], y_coords[ipt], z_coords[ipt]); + const auto wpid = wpid_array[ipt]; + int wpid_ident = wpid.ident(); + + // Check cache first, then populate if needed + auto param_it = cached_params.find(wpid_ident); + if (param_it == cached_params.end()) { + auto wpid_it = wpid_params.find(wpid); + if (wpid_it == wpid_params.end()) { + raise("make_points_cluster_steiner: missing wpid params for wpid %s", wpid.name()); + } + param_it = cached_params.emplace(wpid_ident, wpid_it->second).first; + } + + const auto &[drift_dir, angle_u, angle_v, angle_w] = param_it->second; + const double angle_uvw[3] = {angle_u, angle_v, angle_w}; + + DynamicPointCloud::DPCPoint point; + point.x = pt.x(); + point.y = pt.y(); + point.z = pt.z(); + point.wpid = wpid.ident(); + point.cluster = cluster; + point.blob = nullptr; // Steiner points don't have blob associations + + // Pre-allocate vectors with correct size + point.x_2d.resize(3); + point.y_2d.resize(3); + point.wpid_2d.resize(3); + + if (flag_wrap){ + fill_wrap_points(cluster, pt, wpid, point.x_2d, point.y_2d, point.wpid_2d); + }else{ + for (size_t pindex = 0; pindex < 3; ++pindex) { + point.x_2d[pindex].push_back(point.x); + point.y_2d[pindex].push_back(cos(angle_uvw[pindex]) * point.z - sin(angle_uvw[pindex]) * point.y); + point.wpid_2d[pindex].push_back(wpid.ident()); + } + } + + // Steiner points don't have wire indices, use bogus values + point.wind = wind_bogus; + point.dist_cut = dist_cut_bogus; + + dpc_points.push_back(std::move(point)); + } + + return dpc_points; +} + std::vector Clus::Facade::make_points_direct(const Cluster *cluster, const IDetectorVolumes::pointer dv, const std::map> &wpid_params, std::vector>& points_info, bool flag_wrap){ std::vector dpc_points; diff --git a/clus/src/Facade_Cluster.cxx b/clus/src/Facade_Cluster.cxx index 0b8f2a668..3465d70f0 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_Grouping.cxx b/clus/src/Facade_Grouping.cxx index c9755df7b..b8a0527fe 100644 --- a/clus/src/Facade_Grouping.cxx +++ b/clus/src/Facade_Grouping.cxx @@ -1,6 +1,7 @@ #include "WireCellClus/Facade_Blob.h" #include "WireCellClus/Facade_Cluster.h" #include "WireCellClus/Facade_Grouping.h" +#include "WireCellClus/TrackFitting.h" #include "WireCellAux/PlaneTools.h" #include @@ -72,6 +73,11 @@ std::string Facade::dump(const Facade::Grouping& grouping, int level) // return std::make_tuple(apa, face, plane_index); // } +std::shared_ptr Grouping::get_pr_graph() const +{ + return m_track_fitting ? m_track_fitting->get_graph() : nullptr; +} + void Grouping::on_construct(node_type* node) { this->NaryTree::Facade::on_construct(node); @@ -471,7 +477,7 @@ std::tuple Grouping::convert_3Dpoint_time_ch(const geo_point_t& point, return {tind, wind}; } -std::pair Grouping::convert_time_ch_2Dpoint(const int timeslice, const int channel, const int apa, const int face, const int plane) const +std::pair Grouping::convert_time_ch_2Dpoint(const int timeslice, const int wire, const int apa, const int face, const int plane) const { if (m_anodes.size() == 0) { raise("Anode is null"); @@ -497,7 +503,7 @@ std::pair Grouping::convert_time_ch_2Dpoint(const int timeslice, if (plane >= 0 && plane < nplanes) { const double pitch = pitch_mags.at(apa).at(face).at(plane); const double center = proj_centers.at(apa).at(face).at(plane); - y = pitch * (channel+0.5) + center; + y = pitch * (wire+0.5) + center; } else { raise("invalid plane index %d", plane); diff --git a/clus/src/Facade_Util.cxx b/clus/src/Facade_Util.cxx index 169bf3c4b..4553642ca 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/MultiAlgBlobClustering.cxx b/clus/src/MultiAlgBlobClustering.cxx index 23e54652e..21902eed6 100644 --- a/clus/src/MultiAlgBlobClustering.cxx +++ b/clus/src/MultiAlgBlobClustering.cxx @@ -1,5 +1,6 @@ #include "WireCellClus/MultiAlgBlobClustering.h" #include "WireCellClus/Facade_Summary.h" +#include "WireCellClus/PRSegment.h" #include "WireCellAux/TensorDMpointtree.h" @@ -14,6 +15,7 @@ #include "WireCellUtil/String.h" #include "WireCellUtil/Exceptions.h" #include "WireCellUtil/NamedFactory.h" +#include "WireCellUtil/GraphTools.h" #include #include @@ -27,6 +29,7 @@ using namespace WireCell::Aux; using namespace WireCell::Aux::TensorDM; using namespace WireCell::Clus::Facade; using namespace WireCell::PointCloud::Tree; +using WireCell::GraphTools::mir; MultiAlgBlobClustering::MultiAlgBlobClustering() : Aux::Logger("MultiAlgBlobClustering", "clus") @@ -415,6 +418,119 @@ void MultiAlgBlobClustering::fill_bee_points(const std::string& name, const Grou } } +// Fill bee points from PRGraph track trajectories +void MultiAlgBlobClustering::fill_bee_points_from_pr_graph(const std::string& name, const Grouping& grouping) +{ + if (m_bee_points.find(name) == m_bee_points.end()) { + log->warn("Bee points set '{}' not found for PR graph, skipping", name); + return; + } + + auto& apa_bpts = m_bee_points[name]; + + // Find the configuration for this name + auto it = std::find_if(m_bee_points_configs.begin(), m_bee_points_configs.end(), + [&name](const BeePointsConfig& cfg) { return cfg.name == name; }); + + if (it == m_bee_points_configs.end()) { + log->warn("Configuration for bee points set '{}' not found, skipping", name); + return; + } + + const auto& config = *it; + + // Reset RSE values for all points objects + if (m_use_config_rse) { + apa_bpts.global.rse(m_runNo, m_subRunNo, m_eventNo); + for (auto& [apa, face_map] : apa_bpts.by_apa_face) { + for (auto& [face, bpts] : face_map) { + bpts.rse(m_runNo, m_subRunNo, m_eventNo); + } + } + } else { + // Use the default approach with ident + int run = 0, evt = 0; + if (m_last_ident > 0) { + run = (m_last_ident >> 16) & 0x7fff; + evt = (m_last_ident) & 0xffff; + } + apa_bpts.global.reset(evt, 0, run); + for (auto& [anode_id, face_map] : apa_bpts.by_apa_face) { + for (auto& [face, bpts] : face_map) { + bpts.reset(evt, 0, run); + } + } + } + + // Get the PRGraph from the grouping + auto pr_graph = grouping.get_pr_graph(); + if (!pr_graph) { + log->warn("No PR graph found in grouping for bee points set '{}'", name); + return; + } + + log->debug("Filling bee points '{}' from PR graph with {} vertices and {} edges", + name, boost::num_vertices(*pr_graph), boost::num_edges(*pr_graph)); + + // Iterate through all segments (edges) in the graph + int segment_id = 0; + for (auto edge_desc : mir(boost::edges(*pr_graph))) { + const auto& edge_bundle = (*pr_graph)[edge_desc]; + auto segment = edge_bundle.segment; + + if (!segment) continue; + + // Get the fitted points from this segment + const auto& fits = segment->fits(); + + if (fits.empty()) continue; + + // std::cout << "Segment " << segment_id << " has " << fits.size() << " fitted points." << std::endl; + + log->debug("Segment {} has {} fitted points", segment_id, fits.size()); + + // Process each fitted point + for (const auto& fit : fits) { + if (!fit.valid()) continue; // Skip invalid fits + + // Extract 3D point from fit + const auto& point = fit.point; + + // std::cout << "Test: Segment: " << segment_id << " Point: " << point << " Fit valid: " << fit.valid() << " " << fit.index << " " << fit.range << std::endl; + + + // Get charge (dQ) from fit + double charge = fit.dQ; + if (charge < 0) charge = 0; // Use 0 if charge is invalid + + if (config.individual) { + // Fill individual APA/face bee points + if (fit.paf.first >= 0 && fit.paf.second >= 0) { + int apa = fit.paf.first; + int face = fit.paf.second; + + auto it_apa = apa_bpts.by_apa_face.find(apa); + if (it_apa != apa_bpts.by_apa_face.end()) { + auto it_face = it_apa->second.find(face); + if (it_face != it_apa->second.end()) { + // Append point: (position, charge, cluster_id, type) + it_face->second.append(point, charge, segment_id, 0); + } + } + } + } else { + // Fill global bee points + // Append point: (position, charge, cluster_id, type) + apa_bpts.global.append(point, charge, segment_id, 0); + } + } + + segment_id++; + } + + log->debug("Filled bee points '{}' from {} segments", name, segment_id); +} + // Helper function to fill bee points from a single cluster void MultiAlgBlobClustering::fill_bee_points_from_cluster( @@ -799,15 +915,31 @@ bool MultiAlgBlobClustering::operator()(const input_pointer& ints, output_pointe grouping->enumerate_idents(m_clusters_id_order); } - // for (const auto& config : m_bee_points_configs) { - // if (config.name == "img") continue; - // if (config.visitor != cmeth.name) continue; - // auto gs = ensemble.with_name(config.grouping); - // if (gs.empty()) { - // continue; - // } - // fill_bee_points(config.name, *gs[0]); - // } + // Dump bee points right after specific visitor runs + for (const auto& config : m_bee_points_configs) { + if (config.name == "img") continue; + if (config.visitor.empty() || config.visitor != cmeth.name) continue; + + auto gs = ensemble.with_name(config.grouping); + if (gs.empty()) { + continue; + } + + // Check if this visitor produced a PRGraph that we should save + auto pr_graph = gs[0]->get_pr_graph(); + + // std::cout << "Test: Visitor: " << cmeth.name << " Grouping: " << config.grouping << " " << pr_graph << std::endl; + + if (pr_graph) { + // Fill bee points from PRGraph (for track trajectories) + fill_bee_points_from_pr_graph(config.name, *gs[0]); + // std::cout << "Filled bee points from PR graph for visitor: " << cmeth.name << " grouping: " << config.grouping << std::endl; + } else { + // Fill bee points from clusters normally + fill_bee_points(config.name, *gs[0]); + // std::cout << "Filled bee points from clusters for visitor: " << cmeth.name << " grouping: " << config.grouping << std::endl; + } + } } // @@ -818,10 +950,13 @@ bool MultiAlgBlobClustering::operator()(const input_pointer& ints, output_pointe // - // Fill all configured bee points sets + // Fill all configured bee points sets (except those with visitor-specific handling) for (const auto& config : m_bee_points_configs) { if(config.name == "img") continue; + // Skip configs with visitor specified - they were already handled in the visitor loop + if (!config.visitor.empty()) continue; + auto gs = ensemble.with_name(config.grouping); if (gs.empty()) { continue; diff --git a/clus/src/MyFCN.cxx b/clus/src/MyFCN.cxx new file mode 100644 index 000000000..95f85b2a3 --- /dev/null +++ b/clus/src/MyFCN.cxx @@ -0,0 +1,471 @@ +#include "WireCellClus/MyFCN.h" +#include "WireCellUtil/Units.h" + +#include +#include +#include +#include + +using namespace WireCell; +using namespace WireCell::Clus::PR; + +MyFCN::MyFCN(VertexPtr vtx, bool flag_vtx_constraint, double vtx_constraint_range, + double vertex_protect_dis, double vertex_protect_dis_short_track, double fit_dis) + : vtx(vtx) + , enforce_two_track_fit(false) + , flag_vtx_constraint(flag_vtx_constraint) + , vtx_constraint_range(vtx_constraint_range) + , vertex_protect_dis(vertex_protect_dis) + , vertex_protect_dis_short_track(vertex_protect_dis_short_track) + , fit_dis(fit_dis) +{ + segments.clear(); + vec_points.clear(); +} + +MyFCN::~MyFCN() +{ +} + +void MyFCN::print_points() +{ + for (size_t i = 0; i != vec_points.size(); i++) { + for (size_t j = 0; j != vec_points.at(i).size(); j++) { + std::cout << i << " " << j << " " + << vec_points.at(i).at(j).x() / units::cm << " " + << vec_points.at(i).at(j).y() / units::cm << " " + << vec_points.at(i).at(j).z() / units::cm << std::endl; + } + } +} + +void MyFCN::AddSegment(SegmentPtr sg) +{ + // push in ... + segments.push_back(sg); + { + std::vector pts; + vec_points.push_back(pts); + } + + Facade::geo_point_t center(0, 0, 0); + double min_dis = 1e9; + + // Get fit points from segment + const auto& fits = sg->fits(); + if (fits.empty()) { + Facade::geo_point_t a(0, 0, 0); + vec_PCA_dirs.push_back(std::make_tuple(a, a, a)); + vec_PCA_vals.push_back(std::make_tuple(0, 0, 0)); + vec_centers.push_back(a); + return; + } + + std::vector pts; + for (const auto& fit : fits) { + pts.push_back(fit.point); + } + double length = 0; + if (pts.size() > 1) { + auto front = pts.front(); + auto back = pts.back(); + length = std::sqrt(std::pow(front.x() - back.x(), 2) + + std::pow(front.y() - back.y(), 2) + + std::pow(front.z() - back.z(), 2)); + } + + Facade::geo_point_t vtx_pt = vtx->fit().point; + + for (size_t i = 0; i != pts.size(); i++) { + double dis_to_vertex = std::sqrt(std::pow(pts.at(i).x() - vtx_pt.x(), 2) + + std::pow(pts.at(i).y() - vtx_pt.y(), 2) + + std::pow(pts.at(i).z() - vtx_pt.z(), 2)); + if (length > 3.0 * units::cm) { + if (dis_to_vertex < vertex_protect_dis || dis_to_vertex > fit_dis) continue; + } else { + if (dis_to_vertex < vertex_protect_dis_short_track || dis_to_vertex > fit_dis) continue; + } + + vec_points.back().push_back(pts.at(i)); + if (dis_to_vertex < min_dis) { + center = pts.at(i); + min_dis = dis_to_vertex; + } + } + + // calculate the PCA ... + if (vec_points.back().size() > 1) { + int nsum = vec_points.back().size(); + + // Eigen vectors ... + std::vector PCA_axis(3, Facade::geo_point_t(0, 0, 0)); + double PCA_values[3] = {0, 0, 0}; + + Eigen::Matrix3d cov_matrix = Eigen::Matrix3d::Zero(); + + for (size_t k = 0; k != vec_points.back().size(); k++) { + double dx = vec_points.back().at(k).x() - center.x(); + double dy = vec_points.back().at(k).y() - center.y(); + double dz = vec_points.back().at(k).z() - center.z(); + + cov_matrix(0, 0) += dx * dx; + cov_matrix(0, 1) += dx * dy; + cov_matrix(0, 2) += dx * dz; + cov_matrix(1, 1) += dy * dy; + cov_matrix(1, 2) += dy * dz; + cov_matrix(2, 2) += dz * dz; + } + + cov_matrix(1, 0) = cov_matrix(0, 1); + cov_matrix(2, 0) = cov_matrix(0, 2); + cov_matrix(2, 1) = cov_matrix(1, 2); + + cov_matrix /= nsum; + + Eigen::SelfAdjointEigenSolver eigen_solver(cov_matrix); + Eigen::Vector3d eigen_values = eigen_solver.eigenvalues(); + Eigen::Matrix3d eigen_vectors = eigen_solver.eigenvectors(); + + PCA_values[0] = eigen_values(0) + std::pow(0.15 * units::cm, 2); + PCA_values[1] = eigen_values(1) + std::pow(0.15 * units::cm, 2); + PCA_values[2] = eigen_values(2) + std::pow(0.15 * units::cm, 2); + + for (int i = 0; i != 3; i++) { + double norm = std::sqrt(eigen_vectors(0, i) * eigen_vectors(0, i) + + eigen_vectors(1, i) * eigen_vectors(1, i) + + eigen_vectors(2, i) * eigen_vectors(2, i)); + PCA_axis[i] = Facade::geo_point_t(eigen_vectors(0, i) / norm, + eigen_vectors(1, i) / norm, + eigen_vectors(2, i) / norm); + } + + vec_PCA_dirs.push_back(std::make_tuple(PCA_axis[0], PCA_axis[1], PCA_axis[2])); + vec_PCA_vals.push_back(std::make_tuple(PCA_values[0], PCA_values[1], PCA_values[2])); + vec_centers.push_back(center); + + } else { + Facade::geo_point_t a(0, 0, 0); + vec_PCA_dirs.push_back(std::make_tuple(a, a, a)); + vec_PCA_vals.push_back(std::make_tuple(0, 0, 0)); + if (vec_points.back().size() == 1) { + vec_centers.push_back(vec_points.back().back()); + } else { + vec_centers.push_back(a); + } + } +} + +void MyFCN::update_fit_range(double tmp_vertex_protect_dis, double tmp_vertex_protect_dis_short_track, double tmp_fit_dis) +{ + vertex_protect_dis = tmp_vertex_protect_dis; + vertex_protect_dis_short_track = tmp_vertex_protect_dis_short_track; + fit_dis = tmp_fit_dis; + + std::vector tmp_segments = segments; + segments.clear(); + vec_points.clear(); + for (auto it = tmp_segments.begin(); it != tmp_segments.end(); it++) { + AddSegment(*it); + } +} + +int MyFCN::get_fittable_tracks() +{ + int ncount = 0; + for (size_t i = 0; i != vec_points.size(); i++) { + if (vec_points.at(i).size() > 1) ncount++; + } + return ncount; +} + +std::pair MyFCN::get_seg_info(int i) +{ + if (i < (int)segments.size()) { + return std::make_pair(segments.at(i), vec_points.at(i).size()); + } + return std::make_pair(nullptr, 0); +} + +std::pair MyFCN::FitVertex() +{ + Facade::geo_point_t fit_pos = vtx->fit().point; + bool fit_flag = false; + + int ntracks = get_fittable_tracks(); + int npoints = 0; + + int n_large_angles = 0; + for (size_t i = 0; i != vec_PCA_vals.size(); i++) { + Eigen::Vector3d dir1(std::get<0>(vec_PCA_dirs.at(i)).x(), + std::get<0>(vec_PCA_dirs.at(i)).y(), + std::get<0>(vec_PCA_dirs.at(i)).z()); + for (size_t j = i + 1; j < vec_PCA_vals.size(); j++) { + Eigen::Vector3d dir2(std::get<0>(vec_PCA_dirs.at(j)).x(), + std::get<0>(vec_PCA_dirs.at(j)).y(), + std::get<0>(vec_PCA_dirs.at(j)).z()); + double angle = std::acos(dir1.dot(dir2)) * 180.0 / M_PI; + if (angle > 15) n_large_angles++; + } + } + + if ((ntracks > 2 && n_large_angles > 1) || (ntracks >= 2 && enforce_two_track_fit && n_large_angles >= 1)) { + + // start the fit ... + Eigen::VectorXd temp_pos_3D_init(3), temp_pos_3D(3); // to be fitted + temp_pos_3D_init(0) = fit_pos.x(); + temp_pos_3D_init(1) = fit_pos.y(); + temp_pos_3D_init(2) = fit_pos.z(); + + Eigen::Vector3d b(0, 0, 0); + Eigen::MatrixXd A = Eigen::MatrixXd::Zero(3, 3); + + for (size_t i = 0; i != vec_PCA_vals.size(); i++) { + if (std::get<0>(vec_PCA_vals.at(i)) > 0) { + npoints += vec_points.at(i).size(); + + // fill the matrix ... first row, second column + Eigen::MatrixXd R(3, 3); + R(0, 0) = 0; R(0, 1) = 0; R(0, 2) = 0; + double val1 = std::sqrt(std::get<0>(vec_PCA_vals.at(i)) / std::get<1>(vec_PCA_vals.at(i))); + R(1, 0) = val1 * std::get<1>(vec_PCA_dirs.at(i)).x(); + R(1, 1) = val1 * std::get<1>(vec_PCA_dirs.at(i)).y(); + R(1, 2) = val1 * std::get<1>(vec_PCA_dirs.at(i)).z(); + val1 = std::sqrt(std::get<0>(vec_PCA_vals.at(i)) / std::get<2>(vec_PCA_vals.at(i))); + R(2, 0) = val1 * std::get<2>(vec_PCA_dirs.at(i)).x(); + R(2, 1) = val1 * std::get<2>(vec_PCA_dirs.at(i)).y(); + R(2, 2) = val1 * std::get<2>(vec_PCA_dirs.at(i)).z(); + + Eigen::Vector3d data(vec_centers.at(i).x(), vec_centers.at(i).y(), vec_centers.at(i).z()); + data = R * data; + Eigen::MatrixXd RT = R.transpose(); + + b += RT * data; + A += RT * R; + } + } + + // add constraint ... + if (flag_vtx_constraint) { + Eigen::MatrixXd R = Eigen::MatrixXd::Zero(3, 3); + R(0, 0) = 1.0 / vtx_constraint_range * std::sqrt(npoints); + R(1, 1) = 1.0 / vtx_constraint_range * std::sqrt(npoints); + R(2, 2) = 1.0 / vtx_constraint_range * std::sqrt(npoints); + + Eigen::Vector3d data(fit_pos.x() / vtx_constraint_range * std::sqrt(npoints), + fit_pos.y() / vtx_constraint_range * std::sqrt(npoints), + fit_pos.z() / vtx_constraint_range * std::sqrt(npoints)); + Eigen::MatrixXd RT = R.transpose(); + b += RT * data; + A += RT * R; + } + + Eigen::BiCGSTAB solver; + solver.compute(A); + temp_pos_3D = solver.solveWithGuess(b, temp_pos_3D_init); + + if (!std::isnan(solver.error())) { + fit_pos = Facade::geo_point_t(temp_pos_3D(0), temp_pos_3D(1), temp_pos_3D(2)); + fit_flag = true; + } else { + std::cout << "Cluster: "; + if (vtx->cluster()) { + std::cout << vtx->cluster()->get_cluster_id(); + } else { + std::cout << "unknown"; + } + std::cout << " Fit Vertex Failed!" << std::endl; + } + } + + return std::make_pair(fit_flag, fit_pos); +} + +void MyFCN::UpdateInfo(Facade::geo_point_t fit_pos, Facade::Cluster& temp_cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, double default_dis_cut){ + + // Get PC transform and cluster parameters + auto pcts = track_fitter.get_pc_transforms(); + const auto transform = pcts->pc_transform(temp_cluster.get_scope_transform(temp_cluster.get_default_scope())); + double cluster_t0 = temp_cluster.get_cluster_t0(); + + // Get APA/face for the fit position + auto test_wpid = dv->contained_by(fit_pos); + if (test_wpid.apa() == -1 || test_wpid.face() == -1) { + std::cout << "Warning: fit_pos not contained in detector volume" << std::endl; + return; + } + int apa = test_wpid.apa(); + int face = test_wpid.face(); + + // Get geometry parameters from TrackFitting + const auto& wpid_offsets = track_fitter.get_wpid_offsets(); + const auto& wpid_slopes = track_fitter.get_wpid_slopes(); + + WirePlaneId wpid(kAllLayers, face, apa); + auto offset_it = wpid_offsets.find(wpid); + auto slope_it = wpid_slopes.find(wpid); + + if (offset_it == wpid_offsets.end() || slope_it == wpid_slopes.end()) { + std::cout << "Warning: geometry parameters not found for APA " << apa << " Face " << face << std::endl; + return; + } + + auto offset_t = std::get<0>(offset_it->second); + auto offset_u = std::get<1>(offset_it->second); + auto offset_v = std::get<2>(offset_it->second); + auto offset_w = std::get<3>(offset_it->second); + auto slope_x = std::get<0>(slope_it->second); + auto slope_yu = std::get<1>(slope_it->second).first; + auto slope_zu = std::get<1>(slope_it->second).second; + auto slope_yv = std::get<2>(slope_it->second).first; + auto slope_zv = std::get<2>(slope_it->second).second; + auto slope_yw = std::get<3>(slope_it->second).first; + auto slope_zw = std::get<3>(slope_it->second).second; + + // Transform to raw coordinates for wire plane calculations + auto fit_pos_raw = transform->backward(fit_pos, cluster_t0, face, apa); + auto vtx_pos_raw = transform->backward(vtx->fit().point, cluster_t0, face, apa); + + // Print update information if vertex moved significantly + if ((fit_pos - vtx->fit().point).magnitude() > 0.01 * units::cm) { + std::cout << "Cluster: "; + if (vtx->cluster()) { + std::cout << vtx->cluster()->get_cluster_id(); + } else { + std::cout << "unknown"; + } + std::cout << " Update Vertex: (" + << offset_u + (slope_yu * fit_pos_raw.y() + slope_zu * fit_pos_raw.z()) << ", " + << offset_v + (slope_yv * fit_pos_raw.y() + slope_zv * fit_pos_raw.z()) << ", " + << offset_w + (slope_yw * fit_pos_raw.y() + slope_zw * fit_pos_raw.z()) << ", " + << offset_t + slope_x * fit_pos_raw.x() << ") <- (" + << offset_u + (slope_yu * vtx_pos_raw.y() + slope_zu * vtx_pos_raw.z()) << ", " + << offset_v + (slope_yv * vtx_pos_raw.y() + slope_zv * vtx_pos_raw.z()) << ", " + << offset_w + (slope_yw * vtx_pos_raw.y() + slope_zw * vtx_pos_raw.z()) << ", " + << offset_t + slope_x * vtx_pos_raw.x() << ")" << std::endl; + } + + // Get steiner point cloud from cluster + if (!temp_cluster.has_pc("steiner_pc") || temp_cluster.get_pc("steiner_pc").size() == 0) { + std::cout << "Warning: steiner_pc not found in cluster" << std::endl; + return; + } + const auto& steiner_pc = temp_cluster.get_pc("steiner_pc"); + const auto& coords = temp_cluster.get_default_scope().coords; + const auto& x_coords = steiner_pc.get(coords.at(0))->elements(); + const auto& y_coords = steiner_pc.get(coords.at(1))->elements(); + const auto& z_coords = steiner_pc.get(coords.at(2))->elements(); + + // Find closest steiner point to new fit position + auto vtx_knn_results = temp_cluster.kd_steiner_knn(1, fit_pos, "steiner_pc"); + size_t vtx_new_idx = vtx_knn_results[0].first; + Facade::geo_point_t vtx_new_pt(x_coords[vtx_new_idx], y_coords[vtx_new_idx], z_coords[vtx_new_idx]); + + // Update each segment connected to this vertex + for (size_t i = 0; i != segments.size(); i++) { + auto& seg_wcpts = segments.at(i)->wcpts(); + if (seg_wcpts.empty()) continue; + + // Determine if vertex is at front or back of segment + bool flag_front = false; + double dis_front = (seg_wcpts.front().point - vtx->fit().point).magnitude(); + double dis_back = (seg_wcpts.back().point - vtx->fit().point).magnitude(); + flag_front = (dis_front < dis_back); + + // Find closest wcpt to the PCA center, excluding points too close to vertex + double max_dis = std::max(dis_front, dis_back); + double dis_cut = (max_dis > 2 * default_dis_cut) ? default_dis_cut : 0; + + size_t min_idx = 0; + double min_dis = 1e9; + for (size_t j = 0; j != seg_wcpts.size(); j++) { + double dis_to_center = (seg_wcpts.at(j).point - vec_centers.at(i)).magnitude(); + double dis_to_vtx = (seg_wcpts.at(j).point - vtx->fit().point).magnitude(); + if (dis_to_center < min_dis && dis_to_vtx > dis_cut) { + min_idx = j; + min_dis = dis_to_center; + } + } + + auto& min_wcp = seg_wcpts.at(min_idx); + + // Create new path from new vertex position to closest point using steiner graph + std::list new_list; + new_list.push_back(WCPoint{vtx_new_pt}); + + // Interpolate intermediate points + double dis_step = 2.0 * units::cm; + double total_dis = (vtx_new_pt - min_wcp.point).magnitude(); + int ncount = std::round(total_dis / dis_step); + if (ncount < 2) ncount = 2; + + // double cumulative_ray_length = 0.0; + for (int qx = 1; qx < ncount; qx++) { + Facade::geo_point_t tmp_p( + vtx_new_pt.x() + (min_wcp.point.x() - vtx_new_pt.x()) / ncount * qx, + vtx_new_pt.y() + (min_wcp.point.y() - vtx_new_pt.y()) / ncount * qx, + vtx_new_pt.z() + (min_wcp.point.z() - vtx_new_pt.z()) / ncount * qx + ); + auto tmp_knn_results = temp_cluster.kd_steiner_knn(1, tmp_p, "steiner_pc"); + size_t tmp_idx = tmp_knn_results[0].first; + Facade::geo_point_t tmp_pt(x_coords[tmp_idx], y_coords[tmp_idx], z_coords[tmp_idx]); + + // Skip if too far from target point or duplicate + if ((tmp_pt - tmp_p).magnitude() > 0.3 * units::cm) continue; + double dis_to_last = (tmp_pt - new_list.back().point).magnitude(); + if (dis_to_last > 0.01 * units::cm && (tmp_pt - min_wcp.point).magnitude() > 0.01 * units::cm) { + // cumulative_ray_length += dis_to_last; + new_list.push_back(WCPoint{tmp_pt}); + } + } + // cumulative_ray_length += (min_wcp.point - new_list.back().point).magnitude(); + new_list.push_back(WCPoint{min_wcp.point}); + + // Replace segment path + std::list old_list(seg_wcpts.begin(), seg_wcpts.end()); + + if (flag_front) { + // Remove old path from front to min_wcp + while (!old_list.empty() && (old_list.front().point - min_wcp.point).magnitude() > 0.01 * units::cm) { + old_list.pop_front(); + } + if (!old_list.empty()) old_list.pop_front(); + + // Prepend new path + for (auto it = new_list.rbegin(); it != new_list.rend(); ++it) { + old_list.push_front(*it); + } + } else { + // Remove old path from back to min_wcp + while (!old_list.empty() && (old_list.back().point - min_wcp.point).magnitude() > 0.01 * units::cm) { + old_list.pop_back(); + } + if (!old_list.empty()) old_list.pop_back(); + + // Append new path + for (auto it = new_list.rbegin(); it != new_list.rend(); ++it) { + old_list.push_back(*it); + } + } + + // Update segment wcpts + seg_wcpts.clear(); + seg_wcpts.reserve(old_list.size()); + std::copy(old_list.begin(), old_list.end(), std::back_inserter(seg_wcpts)); + + // Clear fit data - will need to be recalculated + segments.at(i)->clear_fit(dv); + } + + // Update vertex with new fit position and wire coordinates + auto& vtx_fit = vtx->fit(); + vtx_fit.point = fit_pos; + vtx_fit.pu = offset_u + (slope_yu * fit_pos_raw.y() + slope_zu * fit_pos_raw.z()); + vtx_fit.pv = offset_v + (slope_yv * fit_pos_raw.y() + slope_zv * fit_pos_raw.z()); + vtx_fit.pw = offset_w + (slope_yw * fit_pos_raw.y() + slope_zw * fit_pos_raw.z()); + vtx_fit.pt = offset_t + slope_x * fit_pos_raw.x(); + vtx_fit.paf = std::make_pair(apa, face); + vtx_fit.flag_fix = true; + + // Update vertex wcpt + vtx->wcpt().point = vtx_new_pt; +} \ No newline at end of file diff --git a/clus/src/NeutrinoDLVertex.cxx b/clus/src/NeutrinoDLVertex.cxx new file mode 100644 index 000000000..e69de29bb diff --git a/clus/src/NeutrinoDeghoster.cxx b/clus/src/NeutrinoDeghoster.cxx new file mode 100644 index 000000000..e16ca2ae0 --- /dev/null +++ b/clus/src/NeutrinoDeghoster.cxx @@ -0,0 +1,602 @@ +#include "WireCellClus/NeutrinoPatternBase.h" +#include "WireCellClus/PRSegmentFunctions.h" + +using namespace WireCell::Clus::PR; +using namespace WireCell::Clus; + +namespace { + // Helper function to sort clusters by total length in descending order + bool sortbysec(const std::pair& a, + const std::pair& b) { + return (a.second > b.second); + } + + // Helper function to sort segments by length in descending order + bool sortbysec1(const std::pair& a, + const std::pair& b) { + return (a.second > b.second); + } +} + +void PatternAlgorithms::order_clusters(Graph& graph, std::vector& ordered_clusters, std::map >& map_cluster_to_segments, std::map& map_cluster_total_length){ + // Clear output containers + map_cluster_to_segments.clear(); + map_cluster_total_length.clear(); + ordered_clusters.clear(); + + // Iterate through all segments in the graph + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr seg = graph[*eit].segment; + + if (!seg || !seg->cluster()) continue; + + // Get the segment's cluster + Facade::Cluster* cluster = seg->cluster(); + + // Calculate segment length + double length = segment_track_length(seg); + + // Check if this is the first segment for this cluster + if (map_cluster_total_length.find(cluster) == map_cluster_total_length.end()) { + // First segment for this cluster - initialize + std::vector segments; + segments.push_back(seg); + map_cluster_to_segments[cluster] = segments; + map_cluster_total_length[cluster] = length; + } else { + // Add to existing cluster + map_cluster_to_segments[cluster].push_back(seg); + map_cluster_total_length[cluster] += length; + } + } + + // Create a vector of pairs (cluster, total_length) for sorting + std::vector> temp_pair_vec; + for (auto it = map_cluster_total_length.begin(); it != map_cluster_total_length.end(); ++it) { + temp_pair_vec.push_back(std::make_pair(it->first, it->second)); + } + + // Sort clusters by total length in descending order + std::sort(temp_pair_vec.begin(), temp_pair_vec.end(), sortbysec); + + // Fill ordered_clusters with sorted results + for (auto it = temp_pair_vec.begin(); it != temp_pair_vec.end(); ++it) { + ordered_clusters.push_back(it->first); + } +} + +void PatternAlgorithms::deghost_clusters(Graph& graph, std::vector& all_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv){ + // Order clusters by total segment length + std::map> map_cluster_to_segments; + std::map map_cluster_total_length; + std::vector ordered_clusters; + order_clusters(graph, ordered_clusters, map_cluster_to_segments, map_cluster_total_length); + + if (ordered_clusters.empty()) return; + + // Get first cluster's grouping to access wpids and dead channel info + auto* first_grouping = ordered_clusters[0]->grouping(); + if (!first_grouping) return; + + // Build wpid_params + const auto& wpids = first_grouping->wpids(); + std::map> wpid_params; + std::map> wpid_U_dir, wpid_V_dir, wpid_W_dir; + std::set apas; + Facade::compute_wireplane_params(wpids, dv, wpid_params, wpid_U_dir, wpid_V_dir, wpid_W_dir, apas); + + // Get dead channel maps + std::map>>> af_dead_u_index; + std::map>>> af_dead_v_index; + std::map>>> af_dead_w_index; + + for (const auto& wpid : wpids) { + int apa = wpid.apa(); + int face = wpid.face(); + af_dead_u_index[apa][face] = first_grouping->get_dead_winds(apa, face, 0); + af_dead_v_index[apa][face] = first_grouping->get_dead_winds(apa, face, 1); + af_dead_w_index[apa][face] = first_grouping->get_dead_winds(apa, face, 2); + } + + // Create global point clouds + auto global_point_cloud = std::make_shared(wpid_params); + auto global_steiner_point_cloud = std::make_shared(wpid_params); + auto global_skeleton_cloud = std::make_shared(wpid_params); + + // Add points from clusters not in ordered list + for (auto cluster : all_clusters) { + if (map_cluster_total_length.find(cluster) == map_cluster_total_length.end()) { + global_point_cloud->add_points(Facade::make_points_cluster(cluster, wpid_params, true)); + } + } + + std::vector to_be_removed_clusters; + + // Process ordered clusters + for (size_t i = 0; i < ordered_clusters.size(); i++) { + if (i == 0) { + // First cluster: add all its points + global_point_cloud->add_points(Facade::make_points_cluster(ordered_clusters[i], wpid_params, true)); + global_steiner_point_cloud->add_points(Facade::make_points_cluster_steiner(ordered_clusters[i], wpid_params, true)); + + // Add skeleton points from segments + auto it = map_cluster_to_segments.find(ordered_clusters[i]); + if (it != map_cluster_to_segments.end()) { + for (auto seg : it->second) { + // Create point-plane pairs from segment fits + std::vector> point_plane_pairs; + for (const auto& fit : seg->fits()) { + WirePlaneId wpid = dv->contained_by(fit.point); + point_plane_pairs.emplace_back(fit.point, wpid); + } + global_skeleton_cloud->add_points(Facade::make_points_direct(ordered_clusters[i], dv, wpid_params, point_plane_pairs, true)); + } + } + } else { + // Process subsequent clusters + Facade::Cluster* cluster = ordered_clusters[i]; + int num_dead[3] = {0, 0, 0}; + int num_unique[3] = {0, 0, 0}; + int num_total_points = 0; + + double dis_cut = 1.2 * units::cm; + + auto it = map_cluster_to_segments.find(cluster); + if (it != map_cluster_to_segments.end()) { + for (auto seg : it->second) { + for (const auto& fit : seg->fits()) { + Facade::geo_point_t test_point = fit.point; + num_total_points++; + + WirePlaneId test_wpid = dv->contained_by(test_point); + int apa = test_wpid.apa(); + int face = test_wpid.face(); + + // Get point in raw coordinates for dead channel check + auto transform = track_fitter.get_pc_transforms()->pc_transform(cluster->get_scope_transform(cluster->get_default_scope())); + double cluster_t0 = cluster->get_cluster_t0(); + Facade::geo_point_t p_raw = transform->backward(test_point, cluster_t0, face, apa); + + // const auto& winds = cluster->wire_indices(); + + // U plane + bool flag_dead = false; + if (af_dead_u_index.find(apa) != af_dead_u_index.end() && + af_dead_u_index[apa].find(face) != af_dead_u_index[apa].end()) { + flag_dead = cluster->grouping()->get_closest_dead_chs(p_raw, 1, apa, face, 0); + } + + if (!flag_dead) { + auto results = global_point_cloud->get_closest_2d_point_info(test_point, 0, face, apa); + if (std::get<0>(results) <= dis_cut / 2.) { + // Overlap with global point cloud + } else { + results = global_steiner_point_cloud->get_closest_2d_point_info(test_point, 0, face, apa); + if (std::get<0>(results) <= dis_cut * 2. / 3.) { + // Overlap with steiner cloud + } else { + results = global_skeleton_cloud->get_closest_2d_point_info(test_point, 0, face, apa); + if (std::get<0>(results) <= dis_cut * 6. / 4.) { + // Overlap with skeleton cloud + } else { + num_unique[0]++; + } + } + } + } else { + num_dead[0]++; + } + + // V plane + flag_dead = false; + if (af_dead_v_index.find(apa) != af_dead_v_index.end() && + af_dead_v_index[apa].find(face) != af_dead_v_index[apa].end()) { + flag_dead = cluster->grouping()->get_closest_dead_chs(p_raw, 1, apa, face, 1); + } + + if (!flag_dead) { + auto results = global_point_cloud->get_closest_2d_point_info(test_point, 1, face, apa); + if (std::get<0>(results) <= dis_cut / 2.) { + } else { + results = global_steiner_point_cloud->get_closest_2d_point_info(test_point, 1, face, apa); + if (std::get<0>(results) <= dis_cut * 2. / 3.) { + } else { + results = global_skeleton_cloud->get_closest_2d_point_info(test_point, 1, face, apa); + if (std::get<0>(results) <= dis_cut * 6. / 4.) { + } else { + num_unique[1]++; + } + } + } + } else { + num_dead[1]++; + } + + // W plane + flag_dead = false; + if (af_dead_w_index.find(apa) != af_dead_w_index.end() && + af_dead_w_index[apa].find(face) != af_dead_w_index[apa].end()) { + flag_dead = cluster->grouping()->get_closest_dead_chs(p_raw, 1, apa, face, 2); + } + + if (!flag_dead) { + auto results = global_point_cloud->get_closest_2d_point_info(test_point, 2, face, apa); + if (std::get<0>(results) <= dis_cut / 2.) { + } else { + results = global_steiner_point_cloud->get_closest_2d_point_info(test_point, 2, face, apa); + if (std::get<0>(results) <= dis_cut * 2. / 3.) { + } else { + results = global_skeleton_cloud->get_closest_2d_point_info(test_point, 2, face, apa); + if (std::get<0>(results) <= dis_cut * 6. / 4.) { + } else { + num_unique[2]++; + } + } + } + } else { + num_dead[2]++; + } + } + } + } + + // Calculate percentages + bool flag_add = true; + if (num_total_points > 0) { + double unique_percent_u = num_unique[0] * 1.0 / num_total_points; + double unique_percent_v = num_unique[1] * 1.0 / num_total_points; + double unique_percent_w = num_unique[2] * 1.0 / num_total_points; + + double dead_percent_u = num_dead[0] * 1.0 / num_total_points; + double dead_percent_v = num_dead[1] * 1.0 / num_total_points; + double dead_percent_w = num_dead[2] * 1.0 / num_total_points; + + double max_unique_percent = std::max({unique_percent_u, unique_percent_v, unique_percent_w}); + double min_unique_percent = std::min({unique_percent_u, unique_percent_v, unique_percent_w}); + double ave_unique_percent = (unique_percent_u + unique_percent_v + unique_percent_w) / 3.; + double max_dead_percent = std::max({dead_percent_u, dead_percent_v, dead_percent_w}); + + // Apply ghosting criteria + if ((max_dead_percent >= 0.8 && max_unique_percent <= 0.35 && ave_unique_percent <= 0.16 && min_unique_percent <= 0.08) || + (max_unique_percent <= 0.1 && ave_unique_percent <= 0.05 && min_unique_percent <= 0.025) || + (max_dead_percent < 0.8 && max_dead_percent >= 0.7 && max_unique_percent <= 0.2 && ave_unique_percent <= 0.1 && min_unique_percent <= 0.05)) { + flag_add = false; + } + + // Additional check for one dead plane + if ((num_dead[0] == num_total_points || num_dead[1] == num_total_points || num_dead[2] == num_total_points) && + ((num_unique[0] == 0 && num_unique[1] == 0) || (num_unique[0] == 0 && num_unique[2] == 0) || (num_unique[2] == 0 && num_unique[1] == 0)) && + flag_add && max_unique_percent < 0.75) { + flag_add = false; + } + } + + if (flag_add) { + // Add to global clouds + global_point_cloud->add_points(Facade::make_points_cluster(cluster, wpid_params, true)); + global_steiner_point_cloud->add_points(Facade::make_points_cluster_steiner(cluster, wpid_params, true)); + + auto it = map_cluster_to_segments.find(cluster); + if (it != map_cluster_to_segments.end()) { + for (auto seg : it->second) { + std::vector> point_plane_pairs; + for (const auto& fit : seg->fits()) { + WirePlaneId wpid = dv->contained_by(fit.point); + point_plane_pairs.emplace_back(fit.point, wpid); + } + global_skeleton_cloud->add_points(Facade::make_points_direct(cluster, dv, wpid_params, point_plane_pairs, true)); + } + } + } else { + to_be_removed_clusters.push_back(cluster); + } + } + } + + // Remove segments from ghosted clusters + for (auto cluster : to_be_removed_clusters) { + auto it = map_cluster_to_segments.find(cluster); + if (it != map_cluster_to_segments.end()) { + for (auto seg : it->second) { + remove_segment(graph, seg); + } + } + } + + // Clean up orphaned vertices + std::vector tmp_vertices; + auto [vbegin, vend] = boost::vertices(graph); + for (auto vit = vbegin; vit != vend; ++vit) { + VertexPtr vtx = graph[*vit].vertex; + if (vtx && boost::out_degree(*vit, graph) == 0) { + tmp_vertices.push_back(vtx); + } + } + + for (auto vtx : tmp_vertices) { + remove_vertex(graph, vtx); + } +} + +void PatternAlgorithms::order_segments(std::vector& ordered_segments, std::vector& segments){ + // Clear output container + ordered_segments.clear(); + + // Create vector of pairs (segment, length) + std::vector> temp_pair_vec; + for (auto seg : segments) { + double length = segment_track_length(seg); + temp_pair_vec.push_back(std::make_pair(seg, length)); + } + + // Sort by length in descending order + std::sort(temp_pair_vec.begin(), temp_pair_vec.end(), sortbysec1); + + // Fill ordered_segments with sorted results + for (auto it = temp_pair_vec.begin(); it != temp_pair_vec.end(); ++it) { + ordered_segments.push_back(it->first); + } +} + +void PatternAlgorithms::deghost_segments(Graph& graph, std::map map_cluster_main_vertices, std::vector& all_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv) { + // Order clusters by total segment length + std::map> map_cluster_to_segments; + std::map map_cluster_total_length; + std::vector ordered_clusters; + order_clusters(graph, ordered_clusters, map_cluster_to_segments, map_cluster_total_length); + + if (ordered_clusters.empty()) return; + + // Get first cluster's grouping to access wpids + auto* first_grouping = ordered_clusters[0]->grouping(); + if (!first_grouping) return; + + // Build wpid_params + const auto& wpids = first_grouping->wpids(); + std::map> wpid_params; + std::map> wpid_U_dir, wpid_V_dir, wpid_W_dir; + std::set apas; + Facade::compute_wireplane_params(wpids, dv, wpid_params, wpid_U_dir, wpid_V_dir, wpid_W_dir, apas); + + // Create global point clouds + auto global_point_cloud = std::make_shared(wpid_params); + auto global_steiner_point_cloud = std::make_shared(wpid_params); + auto global_skeleton_cloud = std::make_shared(wpid_params); + + // Add points from clusters not in ordered list + for (auto cluster : all_clusters) { + if (map_cluster_total_length.find(cluster) == map_cluster_total_length.end()) { + global_point_cloud->add_points(Facade::make_points_cluster(cluster, wpid_params, true)); + } + } + + if (global_point_cloud->get_points().size() == 0) return; + + double dis_cut = 1.2 * units::cm; + + // Process ordered clusters + for (size_t i = 0; i < ordered_clusters.size(); i++) { + Facade::Cluster* cluster = ordered_clusters[i]; + + // Order segments within this cluster + auto it_cluster = map_cluster_to_segments.find(cluster); + if (it_cluster == map_cluster_to_segments.end()) continue; + + std::vector ordered_segments; + order_segments(ordered_segments, it_cluster->second); + + // Process each segment + for (auto seg : ordered_segments) { + bool flag_add_seg = true; + + // Get segment properties + double medium_dQ_dx = segment_median_dQ_dx(seg); + double length = segment_track_length(seg); + + // Get vertices + auto edesc = seg->get_descriptor(); + auto source_vdesc = boost::source(edesc, graph); + auto target_vdesc = boost::target(edesc, graph); + VertexPtr v1 = graph[source_vdesc].vertex; + VertexPtr v2 = graph[target_vdesc].vertex; + + // Count connections at each vertex + int start_n = boost::out_degree(source_vdesc, graph); + int end_n = boost::out_degree(target_vdesc, graph); + + // Check if this is a terminal segment with low dQ/dx + if ((start_n == 1 || end_n == 1) && medium_dQ_dx < 1.1 * 43e3 / units::cm && length > 3.6 * units::cm) { + int num_dead[3] = {0, 0, 0}; + int num_unique[3] = {0, 0, 0}; + int num_total_points = 0; + + // Check each fit point + for (const auto& fit : seg->fits()) { + Facade::geo_point_t test_point = fit.point; + num_total_points++; + + WirePlaneId test_wpid = dv->contained_by(test_point); + int apa = test_wpid.apa(); + int face = test_wpid.face(); + + // Get point in raw coordinates + auto transform = track_fitter.get_pc_transforms()->pc_transform(cluster->get_scope_transform(cluster->get_default_scope())); + double cluster_t0 = cluster->get_cluster_t0(); + Facade::geo_point_t p_raw = transform->backward(test_point, cluster_t0, face, apa); + + // Check U plane + bool flag_dead = cluster->grouping()->get_closest_dead_chs(p_raw, 1, apa, face, 0); + if (!flag_dead) { + bool flag_in = false; + + auto results = global_point_cloud->get_closest_2d_point_info(test_point, 0, face, apa); + if (std::get<0>(results) <= dis_cut * 2. / 3.) flag_in = true; + + if (global_steiner_point_cloud->get_points().size() != 0) { + results = global_steiner_point_cloud->get_closest_2d_point_info(test_point, 0, face, apa); + if (std::get<0>(results) <= dis_cut * 2. / 3.) flag_in = true; + } + + if (global_skeleton_cloud->get_points().size() != 0) { + results = global_skeleton_cloud->get_closest_2d_point_info(test_point, 0, face, apa); + if (std::get<0>(results) <= dis_cut * 3. / 4.) flag_in = true; + } + + if (!flag_in) num_unique[0]++; + } else { + num_dead[0]++; + } + + // Check V plane + flag_dead = cluster->grouping()->get_closest_dead_chs(p_raw, 1, apa, face, 1); + if (!flag_dead) { + bool flag_in = false; + + auto results = global_point_cloud->get_closest_2d_point_info(test_point, 1, face, apa); + if (std::get<0>(results) <= dis_cut * 2. / 3.) flag_in = true; + + if (global_steiner_point_cloud->get_points().size() != 0) { + results = global_steiner_point_cloud->get_closest_2d_point_info(test_point, 1, face, apa); + if (std::get<0>(results) <= dis_cut * 2. / 3.) flag_in = true; + } + + if (global_skeleton_cloud->get_points().size() != 0) { + results = global_skeleton_cloud->get_closest_2d_point_info(test_point, 1, face, apa); + if (std::get<0>(results) <= dis_cut * 3. / 4.) flag_in = true; + } + + if (!flag_in) num_unique[1]++; + } else { + num_dead[1]++; + } + + // Check W plane + flag_dead = cluster->grouping()->get_closest_dead_chs(p_raw, 1, apa, face, 2); + if (!flag_dead) { + bool flag_in = false; + + auto results = global_point_cloud->get_closest_2d_point_info(test_point, 2, face, apa); + if (std::get<0>(results) <= dis_cut * 2. / 3.) flag_in = true; + + if (global_steiner_point_cloud->get_points().size() != 0) { + results = global_steiner_point_cloud->get_closest_2d_point_info(test_point, 2, face, apa); + if (std::get<0>(results) <= dis_cut * 2. / 3.) flag_in = true; + } + + if (global_skeleton_cloud->get_points().size() != 0) { + results = global_skeleton_cloud->get_closest_2d_point_info(test_point, 2, face, apa); + if (std::get<0>(results) <= dis_cut * 3. / 4.) flag_in = true; + } + + if (!flag_in) num_unique[2]++; + } else { + num_dead[2]++; + } + } + + // If all points overlap with existing clouds, mark for removal + if (num_unique[0] + num_unique[1] + num_unique[2] == 0) { + flag_add_seg = false; + } + + (void) num_total_points; // num_total_points is not used further + } + + if (flag_add_seg) { + // Add segment fits to skeleton cloud + std::vector> point_plane_pairs; + for (const auto& fit : seg->fits()) { + WirePlaneId wpid = dv->contained_by(fit.point); + point_plane_pairs.emplace_back(fit.point, wpid); + } + global_skeleton_cloud->add_points(Facade::make_points_direct(cluster, dv, wpid_params, point_plane_pairs, true)); + } else { + // Protect main vertex - don't remove segment if it's the only one connected to the main vertex + if (map_cluster_main_vertices.find(cluster) != map_cluster_main_vertices.end()) { + VertexPtr main_vtx = map_cluster_main_vertices[cluster]; + + // Check if this segment is connected to the main vertex + auto edesc = seg->get_descriptor(); + auto source_vdesc = boost::source(edesc, graph); + auto target_vdesc = boost::target(edesc, graph); + VertexPtr v1 = graph[source_vdesc].vertex; + VertexPtr v2 = graph[target_vdesc].vertex; + + // If segment connects to main vertex and it's the only segment at that vertex, keep it + if ((v1 == main_vtx && boost::out_degree(source_vdesc, graph) == 1) || + (v2 == main_vtx && boost::out_degree(target_vdesc, graph) == 1)) { + flag_add_seg = true; + } + } + + if (flag_add_seg) { + // Keep the segment to protect main vertex + std::vector> point_plane_pairs; + for (const auto& fit : seg->fits()) { + WirePlaneId wpid = dv->contained_by(fit.point); + point_plane_pairs.emplace_back(fit.point, wpid); + } + global_skeleton_cloud->add_points(Facade::make_points_direct(cluster, dv, wpid_params, point_plane_pairs, true)); + } else { + // Remove segment + remove_segment(graph, seg); + } + } + } + + // Add cluster points to global clouds after processing its segments + global_point_cloud->add_points(Facade::make_points_cluster(cluster, wpid_params, true)); + global_steiner_point_cloud->add_points(Facade::make_points_cluster_steiner(cluster, wpid_params, true)); + } + + // Clean up orphaned vertices + std::vector tmp_vertices; + auto [vbegin, vend] = boost::vertices(graph); + for (auto vit = vbegin; vit != vend; ++vit) { + VertexPtr vtx = graph[*vit].vertex; + if (vtx && boost::out_degree(*vit, graph) == 0) { + tmp_vertices.push_back(vtx); + } + } + + for (auto vtx : tmp_vertices) { + remove_vertex(graph, vtx); + } +} + + +void PatternAlgorithms::deghosting(Graph& graph, std::map map_cluster_main_vertices, std::vector& all_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv ){ + // Call deghost_clusters + deghost_clusters(graph, all_clusters, track_fitter, dv); + + // Call deghost_segments + deghost_segments(graph, map_cluster_main_vertices, all_clusters, track_fitter, dv); + + // Clean up map_cluster_main_vertices by removing clusters whose main vertices no longer have any segments + std::set temp_clusters; + for (auto it = map_cluster_main_vertices.begin(); it != map_cluster_main_vertices.end(); it++) { + Facade::Cluster* cluster = it->first; + VertexPtr vertex = it->second; + + // Check if this vertex still has segments connected to it by checking the graph + auto [vbegin, vend] = boost::vertices(graph); + bool vertex_has_connections = false; + for (auto vit = vbegin; vit != vend; ++vit) { + if (graph[*vit].vertex == vertex) { + if (boost::out_degree(*vit, graph) > 0) { + vertex_has_connections = true; + } + break; + } + } + if (!vertex_has_connections) { + temp_clusters.insert(cluster); + } + } + + // Remove the clusters that no longer have valid main vertices + for (auto it = temp_clusters.begin(); it != temp_clusters.end(); it++) { + map_cluster_main_vertices.erase(*it); + } +} diff --git a/clus/src/NeutrinoEnergyReco.cxx b/clus/src/NeutrinoEnergyReco.cxx new file mode 100644 index 000000000..447e3e442 --- /dev/null +++ b/clus/src/NeutrinoEnergyReco.cxx @@ -0,0 +1,687 @@ +#include "WireCellClus/NeutrinoPatternBase.h" +#include "WireCellClus/PRSegmentFunctions.h" +#include "WireCellClus/PRShowerFunctions.h" + +using namespace WireCell::Clus::PR; +using namespace WireCell::Clus; + + +double PatternAlgorithms::cal_corr_factor(WireCell::Point& pt, TrackFitting& track_fitter, IDetectorVolumes::pointer dv){ + double corr_factor = 1.0; + // So far this is an empty class that needs to be filled with actual logic ... + + // Example 1: Find APA and face using detector volumes + // The WirePlaneId contains apa and face information + WirePlaneId wpid = dv->contained_by(pt); + int apa = wpid.apa(); + int face = wpid.face(); + int plane = wpid.index(); // 0=U, 1=V, 2=W + + // std::cout << "Point at x=" << pt.x()/units::cm << " y=" << pt.y()/units::cm + // << " z=" << pt.z()/units::cm << " cm" << std::endl; + // std::cout << " APA=" << apa << " Face=" << face << " Plane=" << plane << std::endl; + + // Example 2: Find the grouping from track_fitter + // The track_fitter contains a reference to the grouping + auto grouping = track_fitter.grouping(); + + // if (grouping) { + // std::cout << " Found grouping with " << grouping->children().size() << " clusters" << std::endl; + + // // Example: Access detector parameters from grouping cache + // double tick = grouping->get_tick().at(apa).at(face); + // double time_offset = grouping->get_time_offset().at(apa).at(face); + // double drift_speed = grouping->get_drift_speed().at(apa).at(face); + + // std::cout << " Tick=" << tick/units::us << " us, " + // << "TimeOffset=" << time_offset/units::us << " us, " + // << "DriftSpeed=" << drift_speed/(units::mm/units::us) << " mm/us" << std::endl; + + // // Example: Access channel information for a wire index + // int wire_index = 100; // example wire index + // WirePlaneLayer_t layer = static_cast(plane); + + // try { + // auto channel = grouping->get_plane_channel_wind(apa, face, layer, wire_index); + // int channel_ident = channel->ident(); + // std::cout << " Wire " << wire_index << " -> Channel " << channel_ident << std::endl; + // } catch (...) { + // std::cout << " Wire " << wire_index << " not found in this plane" << std::endl; + // } + + // // Example: Access charge data in a region around the point + // // Convert 3D point to time slice and wire index + // auto [time_slice, wire_idx] = grouping->convert_3Dpoint_time_ch(pt, apa, face, plane); + // std::cout << " 3D point converts to: time_slice=" << time_slice + // << ", wire_index=" << wire_idx << std::endl; + + // // Get charge data in a small window around this point + // int time_window = 5; // +/- 5 time slices + // int wire_window = 3; // +/- 3 wires + + // auto charge_map = grouping->get_overlap_good_ch_charge( + // time_slice - time_window, time_slice + time_window, + // wire_idx - wire_window, wire_idx + wire_window, + // apa, face, plane + // ); + + // std::cout << " Found " << charge_map.size() << " charge measurements nearby" << std::endl; + + // // Example: Iterate through charge measurements + // double total_charge = 0; + // for (const auto& [key, value] : charge_map) { + // int t_slice = key.first; + // int w_idx = key.second; + // double charge = value.first; + // double uncertainty = value.second; + // total_charge += charge; + + // // Optionally print details + // // std::cout << " t=" << t_slice << " w=" << w_idx + // // << " Q=" << charge << " +/- " << uncertainty << std::endl; + // } + // std::cout << " Total charge in window: " << total_charge << std::endl; + // } else { + // std::cout << " Warning: No grouping found in track_fitter" << std::endl; + // } + + (void)apa; + (void)face; + (void)plane; + (void)grouping; + + return corr_factor; +} + + +double PatternAlgorithms::cal_kine_charge(ShowerPtr shower, Graph& graph, TrackFitting& track_fitter, IDetectorVolumes::pointer dv){ + (void)graph; // Unused for now + + if (!shower) return 0.0; + + auto grouping = track_fitter.grouping(); + if (!grouping) return 0.0; + + double kine_energy = 0; + + // Recombination and fudge factors based on particle type + double fudge_factor = 0.95; + double recom_factor = 0.7; + + if (shower->get_flag_shower()) { + recom_factor = 0.5; // assume shower + fudge_factor = 0.8; // shower ... + } else if (std::abs(shower->get_particle_type()) == 2212) { + recom_factor = 0.35; // proton + } + + // Collect 2D charge data divided by plane + std::map charge_2d_u; + std::map charge_2d_v; + std::map charge_2d_w; + std::map, std::vector>> map_apa_ch_plane_wires; + + track_fitter.collect_2D_charge(charge_2d_u, charge_2d_v, charge_2d_w, map_apa_ch_plane_wires); + + // Get point clouds from shower + auto pcloud1 = shower->get_pcloud("associate_points"); // associated points + auto pcloud2 = shower->get_pcloud("fit"); // fit points + + if (!pcloud1 && !pcloud2) return 0; + if (!pcloud1 && pcloud2) pcloud1 = pcloud2; + if (!pcloud2 && pcloud1) pcloud2 = pcloud1; + + double sum_u_charge = 0; + double sum_v_charge = 0; + double sum_w_charge = 0; + + double dis_cut = 0.6 * units::cm; + + // Process U plane charges + for (const auto& [coord_key, charge_data] : charge_2d_u) { + int time_slice = coord_key.time; + int channel = coord_key.channel; + int apa = coord_key.apa; + + // Get wire info for this channel + auto apa_ch_key = std::make_pair(apa, channel); + auto wire_it = map_apa_ch_plane_wires.find(apa_ch_key); + if (wire_it == map_apa_ch_plane_wires.end()) continue; + + int face = -1; + for (const auto& [f, plane, wire] : wire_it->second) { + if (plane == 0) { // U plane + face = f; + break; + } + } + if (face < 0) continue; + + // Convert time and channel to 2D point + auto p2d = grouping->convert_time_ch_2Dpoint(time_slice, channel, apa, face, 0); + WireCell::Point test_p2d(p2d.first, p2d.second, 0); + + // Find closest 3D point in point clouds + double dis = 1e9; + size_t point_index = 0; + const Facade::Cluster* closest_cluster = nullptr; + + if (pcloud1) { + auto result = pcloud1->get_closest_2d_point_info(test_p2d, 0, face, apa); + dis = std::get<0>(result); + closest_cluster = std::get<1>(result); + point_index = std::get<2>(result); + } + + if (dis < dis_cut && closest_cluster) { + const auto& points = pcloud1->get_points(); + if (point_index < points.size()) { + WireCell::Point test_p(points[point_index].x, points[point_index].y, points[point_index].z); + double factor = cal_corr_factor(test_p, track_fitter, dv); + sum_u_charge += charge_data.charge * factor; + } + } else if (pcloud2) { + // Try second point cloud + auto result = pcloud2->get_closest_2d_point_info(test_p2d, 0, face, apa); + dis = std::get<0>(result); + closest_cluster = std::get<1>(result); + point_index = std::get<2>(result); + + if (dis < dis_cut && closest_cluster) { + const auto& points = pcloud2->get_points(); + if (point_index < points.size()) { + WireCell::Point test_p(points[point_index].x, points[point_index].y, points[point_index].z); + double factor = cal_corr_factor(test_p, track_fitter, dv); + sum_u_charge += charge_data.charge * factor; + } + } + } + } + + // Process V plane charges + for (const auto& [coord_key, charge_data] : charge_2d_v) { + int time_slice = coord_key.time; + int channel = coord_key.channel; + int apa = coord_key.apa; + + // Get wire info for this channel + auto apa_ch_key = std::make_pair(apa, channel); + auto wire_it = map_apa_ch_plane_wires.find(apa_ch_key); + if (wire_it == map_apa_ch_plane_wires.end()) continue; + + int face = -1; + for (const auto& [f, plane, wire] : wire_it->second) { + if (plane == 1) { // V plane + face = f; + break; + } + } + if (face < 0) continue; + + // Convert time and channel to 2D point + auto p2d = grouping->convert_time_ch_2Dpoint(time_slice, channel, apa, face, 1); + WireCell::Point test_p2d(p2d.first, p2d.second, 0); + + // Find closest 3D point in point clouds + double dis = 1e9; + size_t point_index = 0; + const Facade::Cluster* closest_cluster = nullptr; + + if (pcloud1) { + auto result = pcloud1->get_closest_2d_point_info(test_p2d, 1, face, apa); + dis = std::get<0>(result); + closest_cluster = std::get<1>(result); + point_index = std::get<2>(result); + } + + if (dis < dis_cut && closest_cluster) { + const auto& points = pcloud1->get_points(); + if (point_index < points.size()) { + WireCell::Point test_p(points[point_index].x, points[point_index].y, points[point_index].z); + double factor = cal_corr_factor(test_p, track_fitter, dv); + sum_v_charge += charge_data.charge * factor; + } + } else if (pcloud2) { + // Try second point cloud + auto result = pcloud2->get_closest_2d_point_info(test_p2d, 1, face, apa); + dis = std::get<0>(result); + closest_cluster = std::get<1>(result); + point_index = std::get<2>(result); + + if (dis < dis_cut && closest_cluster) { + const auto& points = pcloud2->get_points(); + if (point_index < points.size()) { + WireCell::Point test_p(points[point_index].x, points[point_index].y, points[point_index].z); + double factor = cal_corr_factor(test_p, track_fitter, dv); + sum_v_charge += charge_data.charge * factor; + } + } + } + } + + // Process W plane charges + for (const auto& [coord_key, charge_data] : charge_2d_w) { + int time_slice = coord_key.time; + int channel = coord_key.channel; + int apa = coord_key.apa; + + // Get wire info for this channel + auto apa_ch_key = std::make_pair(apa, channel); + auto wire_it = map_apa_ch_plane_wires.find(apa_ch_key); + if (wire_it == map_apa_ch_plane_wires.end()) continue; + + int face = -1; + for (const auto& [f, plane, wire] : wire_it->second) { + if (plane == 2) { // W plane + face = f; + break; + } + } + if (face < 0) continue; + + // Convert time and channel to 2D point + auto p2d = grouping->convert_time_ch_2Dpoint(time_slice, channel, apa, face, 2); + WireCell::Point test_p2d(p2d.first, p2d.second, 0); + + // Find closest 3D point in point clouds + double dis = 1e9; + size_t point_index = 0; + const Facade::Cluster* closest_cluster = nullptr; + + if (pcloud1) { + auto result = pcloud1->get_closest_2d_point_info(test_p2d, 2, face, apa); + dis = std::get<0>(result); + closest_cluster = std::get<1>(result); + point_index = std::get<2>(result); + } + + if (dis < dis_cut && closest_cluster) { + const auto& points = pcloud1->get_points(); + if (point_index < points.size()) { + WireCell::Point test_p(points[point_index].x, points[point_index].y, points[point_index].z); + double factor = cal_corr_factor(test_p, track_fitter, dv); + sum_w_charge += charge_data.charge * factor; + } + } else if (pcloud2) { + // Try second point cloud + auto result = pcloud2->get_closest_2d_point_info(test_p2d, 2, face, apa); + dis = std::get<0>(result); + closest_cluster = std::get<1>(result); + point_index = std::get<2>(result); + + if (dis < dis_cut && closest_cluster) { + const auto& points = pcloud2->get_points(); + if (point_index < points.size()) { + WireCell::Point test_p(points[point_index].x, points[point_index].y, points[point_index].z); + double factor = cal_corr_factor(test_p, track_fitter, dv); + sum_w_charge += charge_data.charge * factor; + } + } + } + } + + // Calculate overall charge using weighted average + double charge[3] = {sum_u_charge, sum_v_charge, sum_w_charge}; + double weight[3] = {0.25, 0.25, 1.0}; + + // Find min, max, and median charges + int min_index = 0, max_index = 0, med_index = 0; + double min_charge = 1e9, max_charge = -1e9; + for (int i = 0; i < 3; i++) { + if (min_charge > charge[i]) { + min_charge = charge[i]; + min_index = i; + } + if (max_charge < charge[i]) { + max_charge = charge[i]; + max_index = i; + } + } + + if (min_index != max_index) { + for (int i = 0; i < 3; i++) { + if (i == min_index) continue; + if (i == max_index) continue; + med_index = i; + } + } else { + min_index = 0; + med_index = 1; + max_index = 2; + } + + // Calculate asymmetries + double max_asy = 0; + if (charge[med_index] + charge[max_index] > 0) { + max_asy = std::abs(charge[med_index] - charge[max_index]) / + (charge[med_index] + charge[max_index]); + } + + // Calculate overall charge + double overall_charge = (weight[0]*charge[0] + weight[1]*charge[1] + weight[2]*charge[2]) / + (weight[0] + weight[1] + weight[2]); + + // Exclude maximal charge if asymmetry is too large + if (max_asy > 0.04) { + overall_charge = (weight[med_index] * charge[med_index] + + weight[min_index] * charge[min_index]) / + (weight[med_index] + weight[min_index]); + } + + // Convert charge to kinetic energy + // Using W-value of 23.6 eV per electron-ion pair + kine_energy = overall_charge / recom_factor / fudge_factor * 23.6 / 1e6 * units::MeV; + + return kine_energy; +} + +double PatternAlgorithms::cal_kine_charge(SegmentPtr segment, Graph& graph, TrackFitting& track_fitter, IDetectorVolumes::pointer dv){ + (void)graph; // Unused for now + + if (!segment) return 0.0; + + auto grouping = track_fitter.grouping(); + if (!grouping) return 0.0; + + double kine_energy = 0; + + // Recombination and fudge factors based on particle type + double fudge_factor = 0.95; + double recom_factor = 0.7; + + // Check if segment has shower topology or trajectory flags + if (segment->flags_any(PR::SegmentFlags::kShowerTopology)) { + recom_factor = 0.5; // assume shower + fudge_factor = 0.8; // shower ... + } else if (segment->has_particle_info() && std::abs(segment->particle_info()->pdg()) == 2212) { + recom_factor = 0.35; // proton + } + + // Collect 2D charge data divided by plane + std::map charge_2d_u; + std::map charge_2d_v; + std::map charge_2d_w; + std::map, std::vector>> map_apa_ch_plane_wires; + + track_fitter.collect_2D_charge(charge_2d_u, charge_2d_v, charge_2d_w, map_apa_ch_plane_wires); + + // Get point clouds from segment + auto pcloud1 = segment->dpcloud("associate_points"); // associated points + auto pcloud2 = segment->dpcloud("fit"); // fit points + + if (!pcloud1 && !pcloud2) return 0; + if (!pcloud1 && pcloud2) pcloud1 = pcloud2; + if (!pcloud2 && pcloud1) pcloud2 = pcloud1; + + double sum_u_charge = 0; + double sum_v_charge = 0; + double sum_w_charge = 0; + + double dis_cut = 0.6 * units::cm; + + // Process U plane charges + for (const auto& [coord_key, charge_data] : charge_2d_u) { + int time_slice = coord_key.time; + int channel = coord_key.channel; + int apa = coord_key.apa; + + // Get wire info for this channel + auto apa_ch_key = std::make_pair(apa, channel); + auto wire_it = map_apa_ch_plane_wires.find(apa_ch_key); + if (wire_it == map_apa_ch_plane_wires.end()) continue; + + int face = -1; + for (const auto& [f, plane, wire] : wire_it->second) { + if (plane == 0) { // U plane + face = f; + break; + } + } + if (face < 0) continue; + + // Convert time and channel to 2D point + auto p2d = grouping->convert_time_ch_2Dpoint(time_slice, channel, apa, face, 0); + WireCell::Point test_p2d(p2d.first, p2d.second, 0); + + // Find closest 3D point in point clouds + double dis = 1e9; + size_t point_index = 0; + const Facade::Cluster* closest_cluster = nullptr; + + if (pcloud1) { + auto result = pcloud1->get_closest_2d_point_info(test_p2d, 0, face, apa); + dis = std::get<0>(result); + closest_cluster = std::get<1>(result); + point_index = std::get<2>(result); + } + + if (dis < dis_cut && closest_cluster) { + const auto& points = pcloud1->get_points(); + if (point_index < points.size()) { + WireCell::Point test_p(points[point_index].x, points[point_index].y, points[point_index].z); + double factor = cal_corr_factor(test_p, track_fitter, dv); + sum_u_charge += charge_data.charge * factor; + } + } else if (pcloud2) { + // Try second point cloud + auto result = pcloud2->get_closest_2d_point_info(test_p2d, 0, face, apa); + dis = std::get<0>(result); + closest_cluster = std::get<1>(result); + point_index = std::get<2>(result); + + if (dis < dis_cut && closest_cluster) { + const auto& points = pcloud2->get_points(); + if (point_index < points.size()) { + WireCell::Point test_p(points[point_index].x, points[point_index].y, points[point_index].z); + double factor = cal_corr_factor(test_p, track_fitter, dv); + sum_u_charge += charge_data.charge * factor; + } + } + } + } + + // Process V plane charges + for (const auto& [coord_key, charge_data] : charge_2d_v) { + int time_slice = coord_key.time; + int channel = coord_key.channel; + int apa = coord_key.apa; + + // Get wire info for this channel + auto apa_ch_key = std::make_pair(apa, channel); + auto wire_it = map_apa_ch_plane_wires.find(apa_ch_key); + if (wire_it == map_apa_ch_plane_wires.end()) continue; + + int face = -1; + for (const auto& [f, plane, wire] : wire_it->second) { + if (plane == 1) { // V plane + face = f; + break; + } + } + if (face < 0) continue; + + // Convert time and channel to 2D point + auto p2d = grouping->convert_time_ch_2Dpoint(time_slice, channel, apa, face, 1); + WireCell::Point test_p2d(p2d.first, p2d.second, 0); + + // Find closest 3D point in point clouds + double dis = 1e9; + size_t point_index = 0; + const Facade::Cluster* closest_cluster = nullptr; + + if (pcloud1) { + auto result = pcloud1->get_closest_2d_point_info(test_p2d, 1, face, apa); + dis = std::get<0>(result); + closest_cluster = std::get<1>(result); + point_index = std::get<2>(result); + } + + if (dis < dis_cut && closest_cluster) { + const auto& points = pcloud1->get_points(); + if (point_index < points.size()) { + WireCell::Point test_p(points[point_index].x, points[point_index].y, points[point_index].z); + double factor = cal_corr_factor(test_p, track_fitter, dv); + sum_v_charge += charge_data.charge * factor; + } + } else if (pcloud2) { + // Try second point cloud + auto result = pcloud2->get_closest_2d_point_info(test_p2d, 1, face, apa); + dis = std::get<0>(result); + closest_cluster = std::get<1>(result); + point_index = std::get<2>(result); + + if (dis < dis_cut && closest_cluster) { + const auto& points = pcloud2->get_points(); + if (point_index < points.size()) { + WireCell::Point test_p(points[point_index].x, points[point_index].y, points[point_index].z); + double factor = cal_corr_factor(test_p, track_fitter, dv); + sum_v_charge += charge_data.charge * factor; + } + } + } + } + + // Process W plane charges + for (const auto& [coord_key, charge_data] : charge_2d_w) { + int time_slice = coord_key.time; + int channel = coord_key.channel; + int apa = coord_key.apa; + + // Get wire info for this channel + auto apa_ch_key = std::make_pair(apa, channel); + auto wire_it = map_apa_ch_plane_wires.find(apa_ch_key); + if (wire_it == map_apa_ch_plane_wires.end()) continue; + + int face = -1; + for (const auto& [f, plane, wire] : wire_it->second) { + if (plane == 2) { // W plane + face = f; + break; + } + } + if (face < 0) continue; + + // Convert time and channel to 2D point + auto p2d = grouping->convert_time_ch_2Dpoint(time_slice, channel, apa, face, 2); + WireCell::Point test_p2d(p2d.first, p2d.second, 0); + + // Find closest 3D point in point clouds + double dis = 1e9; + size_t point_index = 0; + const Facade::Cluster* closest_cluster = nullptr; + + if (pcloud1) { + auto result = pcloud1->get_closest_2d_point_info(test_p2d, 2, face, apa); + dis = std::get<0>(result); + closest_cluster = std::get<1>(result); + point_index = std::get<2>(result); + } + + if (dis < dis_cut && closest_cluster) { + const auto& points = pcloud1->get_points(); + if (point_index < points.size()) { + WireCell::Point test_p(points[point_index].x, points[point_index].y, points[point_index].z); + double factor = cal_corr_factor(test_p, track_fitter, dv); + sum_w_charge += charge_data.charge * factor; + } + } else if (pcloud2) { + // Try second point cloud + auto result = pcloud2->get_closest_2d_point_info(test_p2d, 2, face, apa); + dis = std::get<0>(result); + closest_cluster = std::get<1>(result); + point_index = std::get<2>(result); + + if (dis < dis_cut && closest_cluster) { + const auto& points = pcloud2->get_points(); + if (point_index < points.size()) { + WireCell::Point test_p(points[point_index].x, points[point_index].y, points[point_index].z); + double factor = cal_corr_factor(test_p, track_fitter, dv); + sum_w_charge += charge_data.charge * factor; + } + } + } + } + + // Calculate overall charge using weighted average + double charge[3] = {sum_u_charge, sum_v_charge, sum_w_charge}; + double weight[3] = {0.25, 0.25, 1.0}; + + // Find min, max, and median charges + int min_index = 0, max_index = 0, med_index = 0; + double min_charge = 1e9, max_charge = -1e9; + for (int i = 0; i < 3; i++) { + if (min_charge > charge[i]) { + min_charge = charge[i]; + min_index = i; + } + if (max_charge < charge[i]) { + max_charge = charge[i]; + max_index = i; + } + } + + if (min_index != max_index) { + for (int i = 0; i < 3; i++) { + if (i == min_index) continue; + if (i == max_index) continue; + med_index = i; + } + } else { + min_index = 0; + med_index = 1; + max_index = 2; + } + + // Calculate asymmetries + double max_asy = 0; + if (charge[med_index] + charge[max_index] > 0) { + max_asy = std::abs(charge[med_index] - charge[max_index]) / + (charge[med_index] + charge[max_index]); + } + + // Calculate overall charge + double overall_charge = (weight[0]*charge[0] + weight[1]*charge[1] + weight[2]*charge[2]) / + (weight[0] + weight[1] + weight[2]); + + // Exclude maximal charge if asymmetry is too large + if (max_asy > 0.04) { + overall_charge = (weight[med_index] * charge[med_index] + + weight[min_index] * charge[min_index]) / + (weight[med_index] + weight[min_index]); + } + + // Convert charge to kinetic energy + // Using W-value of 23.6 eV per electron-ion pair + kine_energy = overall_charge / recom_factor / fudge_factor * 23.6 / 1e6 * units::MeV; + + return kine_energy; +} + +void PatternAlgorithms::calculate_shower_kinematics(std::set& showers, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, Graph& graph, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model){ + (void)vertices_in_long_muon; // Currently unused + + for (auto& shower : showers) { + if (!shower) continue; + + // Skip if kinematics already calculated + if (shower->get_flag_kinematics()) continue; + + // Get particle type (PDG code) + int particle_type = shower->get_particle_type(); + + // Check if it's a muon (PDG code = ±13) + if (std::abs(particle_type) != 13) { + // Not a muon - use regular kinematics calculation + shower->calculate_kinematics(particle_data, recomb_model); + double kine_charge = cal_kine_charge(shower, graph, track_fitter, dv); + shower->set_kine_charge(kine_charge); + shower->set_flag_kinematics(true); + } else { + // Long muon - use special kinematics calculation + shower->calculate_kinematics_long_muon(segments_in_long_muon, particle_data, recomb_model); + double kine_charge = cal_kine_charge(shower, graph, track_fitter, dv); + shower->set_kine_charge(kine_charge); + shower->set_flag_kinematics(true); + } + } +} \ No newline at end of file diff --git a/clus/src/NeutrinoKinematics.cxx b/clus/src/NeutrinoKinematics.cxx new file mode 100644 index 000000000..e69de29bb diff --git a/clus/src/NeutrinoOtherSegments.cxx b/clus/src/NeutrinoOtherSegments.cxx new file mode 100644 index 000000000..b8ef136ce --- /dev/null +++ b/clus/src/NeutrinoOtherSegments.cxx @@ -0,0 +1,523 @@ +#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()) { + if (candidates_special_A.size() > 1 && ncounts[i] > 6) { + // Use PCA to find the best connection point + std::vector tmp_points; + for (int j = 0; j < ncounts[i]; j++) { + Facade::geo_point_t tmp_p(x_coords[sep_clusters[i][j]], + y_coords[sep_clusters[i][j]], + z_coords[sep_clusters[i][j]]); + tmp_points.push_back(tmp_p); + } + + auto results_pca = calc_PCA_main_axis(tmp_points); + double max_val = 0; + + for (size_t j = 0; j < candidates_special_A.size(); j++) { + size_t idx = candidates_special_A[j]; + double val = std::abs( + (x_coords[idx] - results_pca.first.x()) * results_pca.second.x() + + (y_coords[idx] - results_pca.first.y()) * results_pca.second.y() + + (z_coords[idx] - results_pca.first.z()) * results_pca.second.z() + ); + + if (val > max_val) { + max_val = val; + special_A = idx; + } + } + } else { + special_A = candidates_special_A.front(); + } + } + + // Find furthest point (special_B) + 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 000000000..8c44e3a95 --- /dev/null +++ b/clus/src/NeutrinoPatternBase.cxx @@ -0,0 +1,1596 @@ +#include "WireCellClus/NeutrinoPatternBase.h" +#include "WireCellClus/PRSegmentFunctions.h" +#include + +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; + } + } +} + + +std::pair PatternAlgorithms::calc_PCA_main_axis(std::vector& points){ + Facade::geo_point_t center(0, 0, 0); + int nsum = 0; + + // Calculate the center point + for (size_t i = 0; i < points.size(); i++) { + center += points[i]; + nsum++; + } + + Facade::geo_vector_t PCA_main_axis(0, 0, 0); + + // Need at least 3 points for PCA + if (nsum < 3) { + center.set(0, 0, 0); + return std::make_pair(center, PCA_main_axis); + } + + center /= nsum; + + // Build covariance matrix + Eigen::MatrixXd cov_matrix(3, 3); + + for (int i = 0; i != 3; i++) { + for (int j = i; j != 3; j++) { + cov_matrix(i, j) = 0; + for (size_t k = 0; k < points.size(); k++) { + cov_matrix(i, j) += (points[k][i] - center[i]) * (points[k][j] - center[j]); + } + } + } + + // Fill in symmetric part + cov_matrix(1, 0) = cov_matrix(0, 1); + cov_matrix(2, 0) = cov_matrix(0, 2); + cov_matrix(2, 1) = cov_matrix(1, 2); + + // Compute eigenvalues and eigenvectors + Eigen::SelfAdjointEigenSolver eigenSolver(cov_matrix); + auto eigen_vectors = eigenSolver.eigenvectors(); + + // Get the principal component (largest eigenvalue, which is index 2 in ascending order) + int i = 2; // Eigen returns eigenvalues in ascending order, so index 2 is the largest + double norm = sqrt(eigen_vectors(0, i) * eigen_vectors(0, i) + + eigen_vectors(1, i) * eigen_vectors(1, i) + + eigen_vectors(2, i) * eigen_vectors(2, i)); + + PCA_main_axis.set(eigen_vectors(0, i) / norm, + eigen_vectors(1, i) / norm, + eigen_vectors(2, i) / norm); + + return std::make_pair(center, PCA_main_axis); +} + + +Facade::geo_vector_t PatternAlgorithms::calc_dir_cluster(Graph& graph, Facade::Cluster& cluster, const Facade::geo_point_t& orig_p, double dis_cut){ + Facade::geo_point_t ave_p(0, 0, 0); + int num = 0; + + // Iterate through all segments in the graph that belong to this cluster + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr sg = graph[*eit].segment; + if (!sg || sg->cluster() != &cluster) continue; + + // Get points from this segment (skip first and last points as in original) + const auto& wcpts = sg->wcpts(); + for (size_t i = 1; i + 1 < wcpts.size(); i++) { + const WireCell::Point& pt = wcpts[i].point; + double dis = std::sqrt(std::pow(pt.x() - orig_p.x(), 2) + + std::pow(pt.y() - orig_p.y(), 2) + + std::pow(pt.z() - orig_p.z(), 2)); + + if (dis < dis_cut) { + ave_p.set(ave_p.x() + pt.x(), ave_p.y() + pt.y(), ave_p.z() + pt.z()); + num++; + } + } + } + + // Iterate through all vertices in the graph that belong to this cluster + auto [vbegin, vend] = boost::vertices(graph); + for (auto vit = vbegin; vit != vend; ++vit) { + VertexPtr vtx = graph[*vit].vertex; + if (!vtx || vtx->cluster() != &cluster) continue; + + // Get vertex position (prefer fit point if available) + WireCell::Point vtx_pt = vtx->fit().valid() ? vtx->fit().point : vtx->wcpt().point; + + double dis = std::sqrt(std::pow(vtx_pt.x() - orig_p.x(), 2) + + std::pow(vtx_pt.y() - orig_p.y(), 2) + + std::pow(vtx_pt.z() - orig_p.z(), 2)); + + if (dis < dis_cut) { + ave_p.set(ave_p.x() + vtx_pt.x(), ave_p.y() + vtx_pt.y(), ave_p.z() + vtx_pt.z()); + num++; + } + } + + // Calculate direction vector + Facade::geo_vector_t dir(0, 0, 0); + + if (num > 0) { + // Calculate average position + ave_p.set(ave_p.x() / num, ave_p.y() / num, ave_p.z() / num); + + // Calculate direction from origin to average + dir.set(ave_p.x() - orig_p.x(), ave_p.y() - orig_p.y(), ave_p.z() - orig_p.z()); + + // Normalize to unit vector + double magnitude = std::sqrt(dir.x() * dir.x() + dir.y() * dir.y() + dir.z() * dir.z()); + if (magnitude > 0) { + dir.set(dir.x() / magnitude, dir.y() / magnitude, dir.z() / magnitude); + } + } + + return dir; +} + + + Facade::Cluster* PatternAlgorithms::swap_main_cluster(Facade::Cluster& new_main_cluster, Facade::Cluster& old_main_cluster, std::vector& other_clusters){ + // Remove main_cluster flag from old main cluster (set to 0 to unset) + old_main_cluster.set_flag(Facade::Flags::main_cluster, 0); + + // Add old main cluster to other_clusters + other_clusters.push_back(&old_main_cluster); + + // Set main_cluster flag on new main cluster + new_main_cluster.set_flag(Facade::Flags::main_cluster); + + // Remove new_main_cluster from other_clusters + auto it = std::find_if(other_clusters.begin(), other_clusters.end(), + [&new_main_cluster](Facade::Cluster* c) { + return c == &new_main_cluster; + }); + + if (it != other_clusters.end()) { + other_clusters.erase(it); + } + + return &new_main_cluster; + } + + void PatternAlgorithms::examine_main_vertices(Graph& graph, std::map map_cluster_main_vertices, Facade::Cluster* main_cluster, std::vector& other_clusters){ + if (!main_cluster) return; + + // Calculate cluster length cut + double main_cluster_length = main_cluster->get_length(); + double cluster_length_cut = std::min(main_cluster_length * 0.6, 6.0 * units::cm); + + // First pass: remove short clusters without strong tracks + std::vector clusters_to_be_removed; + + for (auto& [cluster, vertex] : map_cluster_main_vertices) { + if (!cluster || !vertex) continue; + + double length = cluster->get_length(); + + if (length < cluster_length_cut) { + bool flag_removed = true; + + // Check segments connected to this vertex + if (vertex->descriptor_valid()) { + auto vd = vertex->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr seg = graph[*e_it].segment; + if (!seg) continue; + + bool is_shower = seg->flags_any(SegmentFlags::kShowerTrajectory) || + seg->flags_any(SegmentFlags::kShowerTopology); + int dirsign = seg->dirsign(); + bool is_dir_weak = seg->dir_weak(); + double median_dqdx = segment_median_dQ_dx(seg) / (43e3 / units::cm); + + // Keep if: not shower AND has direction AND (strong direction OR high dQ/dx) + if (!is_shower && dirsign != 0 && (!is_dir_weak || median_dqdx > 2.0)) { + flag_removed = false; + break; + } + } + } + + // Additional check for very short clusters + if (!flag_removed) { + if (length < 5 * units::cm) { + WireCell::Point vtx_pt = vertex->fit().valid() ? vertex->fit().point : vertex->wcpt().point; + auto knn = main_cluster->kd_steiner_knn(1, vtx_pt, "steiner_pc"); + if (!knn.empty()) { + double closest_dis = std::sqrt(knn[0].second); + if (closest_dis > 100 * units::cm) { + flag_removed = true; + } + } + } + } + + if (flag_removed) { + clusters_to_be_removed.push_back(cluster); + } + } else { + // For longer clusters, check if very far from main cluster + WireCell::Point vtx_pt = vertex->fit().valid() ? vertex->fit().point : vertex->wcpt().point; + auto knn = main_cluster->kd_steiner_knn(1, vtx_pt, "steiner_pc"); + if (!knn.empty()) { + double closest_dis = std::sqrt(knn[0].second); + if (closest_dis > 200 * units::cm) { + clusters_to_be_removed.push_back(cluster); + } + } + } + } + + // Remove flagged clusters + for (auto cluster : clusters_to_be_removed) { + map_cluster_main_vertices.erase(cluster); + } + clusters_to_be_removed.clear(); + + // Second pass: additional cuts if main cluster has a main vertex + if (map_cluster_main_vertices.find(main_cluster) != map_cluster_main_vertices.end()) { + // Check which clusters have only showers + std::map map_cluster_id_shower; + for (auto& [cluster, vertex] : map_cluster_main_vertices) { + if (cluster) { + map_cluster_id_shower[cluster->get_cluster_id()] = true; + } + } + + // Check all segments to see if any cluster has non-shower segments + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr sg = graph[*eit].segment; + if (!sg) continue; + + int cluster_id = sg->cluster() ? sg->cluster()->get_cluster_id() : -1; + if (map_cluster_id_shower.find(cluster_id) == map_cluster_id_shower.end()) continue; + + bool is_shower = sg->flags_any(SegmentFlags::kShowerTrajectory) || + sg->flags_any(SegmentFlags::kShowerTopology); + if (!is_shower) { + map_cluster_id_shower[cluster_id] = false; + } + } + + bool flag_main_vertex_all_showers = map_cluster_id_shower[main_cluster->get_cluster_id()]; + + if (flag_main_vertex_all_showers) { + // Calculate direction from main cluster's main vertex + VertexPtr main_vtx = map_cluster_main_vertices[main_cluster]; + WireCell::Point main_vtx_pt = main_vtx->fit().valid() ? main_vtx->fit().point : main_vtx->wcpt().point; + Facade::geo_vector_t dir_main = calc_dir_cluster(graph, *main_cluster, main_vtx_pt, 15 * units::cm); + + for (auto& [cluster, vertex] : map_cluster_main_vertices) { + if (cluster == main_cluster || !vertex) continue; + + WireCell::Point vtx_pt = vertex->fit().valid() ? vertex->fit().point : vertex->wcpt().point; + + // Get closest distance to main cluster + auto knn = main_cluster->kd_steiner_knn(1, vtx_pt, "steiner_pc"); + double closest_dis = knn.empty() ? 1e9 : std::sqrt(knn[0].second); + + // Calculate direction vector from main vertex to this vertex + Facade::geo_vector_t dir1(vtx_pt.x() - main_vtx_pt.x(), + vtx_pt.y() - main_vtx_pt.y(), + vtx_pt.z() - main_vtx_pt.z()); + + double angle = 0; + if (dir_main.magnitude() > 0 && dir1.magnitude() > 0) { + double cos_angle = std::clamp(dir_main.dot(dir1) / (dir_main.magnitude() * dir1.magnitude()), -1.0, 1.0); + angle = std::acos(cos_angle) / M_PI * 180.0; + } + + double cluster_length = cluster->get_length(); + + if (angle < 10) { + // Cluster in same direction as main - check if small and close + if ((cluster_length < 15 * units::cm && closest_dis < 40 * units::cm) || + (cluster_length < 7 * units::cm && closest_dis < 60 * units::cm)) { + clusters_to_be_removed.push_back(cluster); + } + } else if (angle > 160) { + // Cluster in opposite direction - check for main cluster swap + if (map_cluster_id_shower[cluster->get_cluster_id()] && + cluster_length > 10 * units::cm && + cluster_length > 0.5 * main_cluster_length) { + + // Calculate direction from this cluster + Facade::geo_vector_t dir2 = calc_dir_cluster(graph, *cluster, vtx_pt, 15 * units::cm); + + // Get closest distance between point clouds + auto closest_result = main_cluster->get_closest_points(*cluster); + double closest_dis_pc = std::get<2>(closest_result); + + double angle2 = 0; + if (dir2.magnitude() > 0 && dir_main.magnitude() > 0) { + double cos_angle2 = std::clamp(dir2.dot(dir_main) / (dir2.magnitude() * dir_main.magnitude()), -1.0, 1.0); + angle2 = std::acos(cos_angle2) / M_PI * 180.0; + } + + if (closest_dis_pc < 10 * units::cm && angle2 < 25) { + // Swap main cluster + main_cluster = swap_main_cluster(*cluster, *main_cluster, other_clusters); + } + } + } + } + } + + // Remove flagged clusters + for (auto cluster : clusters_to_be_removed) { + map_cluster_main_vertices.erase(cluster); + } + clusters_to_be_removed.clear(); + } + } \ No newline at end of file diff --git a/clus/src/NeutrinoShowerClustering.cxx b/clus/src/NeutrinoShowerClustering.cxx new file mode 100644 index 000000000..d367caf19 --- /dev/null +++ b/clus/src/NeutrinoShowerClustering.cxx @@ -0,0 +1,2962 @@ +#include "WireCellClus/NeutrinoPatternBase.h" +#include "WireCellClus/PRSegmentFunctions.h" +#include "WireCellClus/PRShowerFunctions.h" +#include + +using namespace WireCell::Clus::PR; +using namespace WireCell::Clus; + +namespace { + struct cluster_point_info { + Facade::Cluster* cluster; + double min_angle; + double min_dis; + VertexPtr min_vertex; + WireCell::Point min_point; + }; + + bool sortbydis(const cluster_point_info &a, const cluster_point_info &b) { + return (a.min_dis < b.min_dis); + } +} + +void PatternAlgorithms::update_shower_maps(std::set& showers, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters){ + // Clear all maps + map_vertex_to_shower.clear(); + map_vertex_in_shower.clear(); + map_segment_in_shower.clear(); + used_shower_clusters.clear(); + + // Iterate through all showers + for (auto shower : showers) { + // Map start vertex to shower + auto [start_vtx, conn_type] = shower->get_start_vertex_and_type(); + if (start_vtx) { + map_vertex_to_shower[start_vtx].insert(shower); + } + + // Fill maps using TrajectoryView - iterate through all vertices and segments in the shower + TrajectoryView& traj = shower->fill_maps(); + + // Fill map_vertex_in_shower with all vertices in this shower + for (auto vdesc : traj.nodes()) { + auto vtx = traj.view_graph()[vdesc].vertex; + if (vtx) { + map_vertex_in_shower[vtx] = shower; + } + } + + // Fill map_segment_in_shower with all segments in this shower + for (auto edesc : traj.edges()) { + auto seg = traj.view_graph()[edesc].segment; + if (seg) { + map_segment_in_shower[seg] = shower; + } + } + } + + // Collect all cluster IDs from segments in the map + for (auto it = map_segment_in_shower.begin(); it != map_segment_in_shower.end(); it++) { + auto seg = it->first; + if (seg && seg->cluster()) { + used_shower_clusters.insert(seg->cluster()); + } + } +} + +void PatternAlgorithms::shower_clustering_with_nv_in_main_cluster(Graph& graph, VertexPtr main_vertex, std::set& showers, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, std::set& vertices_in_long_muon, std::set& segments_in_long_muon){ + if (!main_vertex) return; + + // Build map_vertex_segments from graph + std::map> map_vertex_segments; + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr seg = graph[*eit].segment; + if (!seg) continue; + + auto source_vdesc = boost::source(*eit, graph); + auto target_vdesc = boost::target(*eit, graph); + VertexPtr v1 = graph[source_vdesc].vertex; + VertexPtr v2 = graph[target_vdesc].vertex; + + if (v1) map_vertex_segments[v1].insert(seg); + if (v2) map_vertex_segments[v2].insert(seg); + } + + // Step 1: Collect segments from showers starting at main_vertex + std::set used_segments; + for (auto shower : showers) { + auto [start_vtx, conn_type] = shower->get_start_vertex_and_type(); + if (start_vtx == main_vertex) { + // Collect all segments from this shower + for (auto edesc : shower->edges()) { + auto seg = shower->view_graph()[edesc].segment; + if (seg) { + used_segments.insert(seg); + } + } + + // If there's only 1 segment and it's short, clear used_segments + if (used_segments.size() == 1 && segment_track_length(*used_segments.begin()) < 8 * units::cm) { + used_segments.clear(); + } + } + } + + // Step 2: If used_segments is empty, try to create new showers + if (used_segments.empty()) { + std::set del_showers; + std::map map_shower_max_sg; + ShowerPtr max_shower = nullptr; + double max_length = 0; + + // Get segments connected to main_vertex + auto& main_vtx_segments = map_vertex_segments[main_vertex]; + + for (auto sg : main_vtx_segments) { + // Skip if segment is in a shower + if (map_segment_in_shower.find(sg) != map_segment_in_shower.end()) continue; + + // Skip segments in long muon + if (segments_in_long_muon.find(sg) != segments_in_long_muon.end()) continue; + + // Get segment properties + double medium_dQ_dx = segment_median_dQ_dx(sg); + double medium_dQ_dx_1 = medium_dQ_dx / (43e3 / units::cm); + + // Get particle type if available + int particle_type = 0; + if (sg->has_particle_info() && sg->particle_info()) { + particle_type = sg->particle_info()->pdg(); + } + + // Skip segments with certain particle types and high dQ/dx + if ((particle_type == 11) || + (particle_type == 2212 && (medium_dQ_dx_1 > 1.45 || medium_dQ_dx_1 > 2.7)) || + (particle_type == 211 && medium_dQ_dx_1 > 2.0)) { + continue; + } + + // Create a new shower + ShowerPtr shower = std::make_shared(graph); + shower->set_start_vertex(main_vertex, 1); + shower->set_start_segment(sg); + shower->complete_structure_with_start_segment(used_segments); + + // Analyze shower structure + int n_tracks = 0; + int n_showers = 0; + double total_length = 0; + double max_seg_length = 0; + SegmentPtr max_sg = nullptr; + bool flag_good_track = false; + + // Iterate through shower segments + for (auto edesc : shower->edges()) { + auto sg1 = shower->view_graph()[edesc].segment; + if (!sg1) continue; + + double length = segment_track_length(sg1); + double medium_dQ_dx_sg = segment_median_dQ_dx(sg1); + double medium_dQ_dx_norm = medium_dQ_dx_sg / (43e3 / units::cm); + + // Check if segment is a shower + bool is_shower = sg1->flags_any(SegmentFlags::kShowerTrajectory) || + sg1->flags_any(SegmentFlags::kShowerTopology); + if (is_shower) n_showers++; + + n_tracks++; + total_length += length; + + if (max_seg_length < length) { + max_seg_length = length; + max_sg = sg1; + } + + // Check for good track properties + if (!sg1->dir_weak() && (length > 3.6 * units::cm || (length > 2.4 * units::cm && medium_dQ_dx_norm > 2.5))) { + // Find end vertex of this segment + auto seg_edesc = sg1->get_descriptor(); + auto source_vdesc = boost::source(seg_edesc, graph); + auto target_vdesc = boost::target(seg_edesc, graph); + VertexPtr v1 = graph[source_vdesc].vertex; + VertexPtr v2 = graph[target_vdesc].vertex; + + // Determine end vertex based on dirsign + VertexPtr end_vertex = nullptr; + if (sg1->dirsign() == 1) { + // Check which vertex is at the end + if (!sg1->fits().empty() && v1 && v2) { + auto& fits = sg1->fits(); + double dist1 = (fits.back().point - (v1->fit().valid() ? v1->fit().point : v1->wcpt().point)).magnitude(); + double dist2 = (fits.back().point - (v2->fit().valid() ? v2->fit().point : v2->wcpt().point)).magnitude(); + end_vertex = (dist1 < dist2) ? v1 : v2; + } + } else if (sg1->dirsign() == -1) { + if (!sg1->fits().empty() && v1 && v2) { + auto& fits = sg1->fits(); + double dist1 = (fits.front().point - (v1->fit().valid() ? v1->fit().point : v1->wcpt().point)).magnitude(); + double dist2 = (fits.front().point - (v2->fit().valid() ? v2->fit().point : v2->wcpt().point)).magnitude(); + end_vertex = (dist1 < dist2) ? v1 : v2; + } + } + + if (end_vertex && map_vertex_segments.find(end_vertex) != map_vertex_segments.end()) { + if (map_vertex_segments[end_vertex].size() > 1) { + bool flag_non_ele = false; + for (auto sg2 : map_vertex_segments[end_vertex]) { + if (sg2 == sg1) continue; + bool is_shower2 = sg2->flags_any(SegmentFlags::kShowerTrajectory) || + sg2->flags_any(SegmentFlags::kShowerTopology); + if (!is_shower2) flag_non_ele = true; + } + + if (!flag_non_ele && map_vertex_segments[end_vertex].size() <= 3) { + flag_good_track = true; + } + } else { + flag_good_track = true; + } + } + } + } + + (void) n_showers; // n_showers is not used further + + // Count vertex types in shower + int n_multi_vtx = 0; + int n_two_vtx = 0; + std::map vtx_segment_count; + + for (auto edesc : shower->edges()) { + auto seg = shower->view_graph()[edesc].segment; + if (!seg) continue; + + auto seg_edesc = seg->get_descriptor(); + auto source_vdesc = boost::source(seg_edesc, graph); + auto target_vdesc = boost::target(seg_edesc, graph); + VertexPtr v1 = graph[source_vdesc].vertex; + VertexPtr v2 = graph[target_vdesc].vertex; + + if (v1) vtx_segment_count[v1]++; + if (v2) vtx_segment_count[v2]++; + } + + for (auto& [vtx, count] : vtx_segment_count) { + if (count == 2) n_two_vtx++; + else if (count > 2) n_multi_vtx++; + } + + // Apply selection criteria + if (!flag_good_track && n_multi_vtx > 0 && max_seg_length < 65 * units::cm && + ((total_length < n_tracks * 27 * units::cm && total_length < 85 * units::cm) || + (total_length < n_tracks * 18 * units::cm && total_length < 95 * units::cm)) && + n_two_vtx < 3) { + + map_shower_max_sg[shower] = max_sg; + if (shower->get_total_length() > max_length) { + max_shower = shower; + max_length = shower->get_total_length(); + } + } + } + + // Process selected showers + for (auto& [shower, max_sg] : map_shower_max_sg) { + if (shower == max_shower) { + // Convert to EM shower (particle type 11 = electron) + if (shower->start_segment() && shower->start_segment()->has_particle_info() && shower->start_segment()->particle_info()) { + shower->start_segment()->particle_info()->set_pdg(11); + } + + // Set avoid muon check flag on max segment + if (max_sg) { + max_sg->set_flags(SegmentFlags::kAvoidMuonCheck); + } + + // Add shower to collection + showers.insert(shower); + + // Check for showers to delete that conflict with new shower + std::set shower_vertices; + for (auto vdesc : shower->nodes()) { + auto vtx = shower->view_graph()[vdesc].vertex; + if (vtx) shower_vertices.insert(vtx); + } + + for (auto shower1 : showers) { + if (shower == shower1) continue; + auto [start_vtx1, conn_type1] = shower1->get_start_vertex_and_type(); + if (conn_type1 == 1 && start_vtx1 && shower_vertices.find(start_vtx1) != shower_vertices.end()) { + del_showers.insert(shower1); + } + } + } + } + + // Delete conflicting showers + for (auto shower1 : del_showers) { + auto [start_vtx1, conn_type1] = shower1->get_start_vertex_and_type(); + if (start_vtx1 != main_vertex) { + showers.erase(shower1); + } + } + + // Update shower maps + update_shower_maps(showers, map_vertex_in_shower, map_segment_in_shower, map_vertex_to_shower, used_shower_clusters); + } +} + +void PatternAlgorithms::shower_clustering_connecting_to_main_vertex(Graph& graph, VertexPtr main_vertex, std::set& showers, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters){ + if (!main_vertex) return; + + // Build map_vertex_segments from graph + std::map> map_vertex_segments; + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr seg = graph[*eit].segment; + if (!seg) continue; + + auto source_vdesc = boost::source(*eit, graph); + auto target_vdesc = boost::target(*eit, graph); + VertexPtr v1 = graph[source_vdesc].vertex; + VertexPtr v2 = graph[target_vdesc].vertex; + + if (v1) map_vertex_segments[v1].insert(seg); + if (v2) map_vertex_segments[v2].insert(seg); + } + + // Step 1: Collect segments from showers starting at main_vertex + std::set used_segments; + for (auto shower : showers) { + auto [start_vtx, conn_type] = shower->get_start_vertex_and_type(); + if (start_vtx == main_vertex) { + // Collect all segments from this shower + for (auto edesc : shower->edges()) { + auto seg = shower->view_graph()[edesc].segment; + if (seg) { + used_segments.insert(seg); + } + } + + // If there's only 1 segment and it's short, clear used_segments + if (used_segments.size() == 1 && segment_track_length(*used_segments.begin()) < 8 * units::cm) { + used_segments.clear(); + } + } + } + + // Step 2: If used_segments is empty, try to create new showers + if (used_segments.empty()) { + std::set del_showers; + std::map map_shower_max_sg; + ShowerPtr max_shower = nullptr; + double max_length = 0; + + // Get segments connected to main_vertex + auto& main_vtx_segments = map_vertex_segments[main_vertex]; + + for (auto sg : main_vtx_segments) { + // Calculate number of daughter showers for this segment + auto pair_result = calculate_num_daughter_showers(graph, main_vertex, sg, true); + + // Skip if segment is in a shower + if (map_segment_in_shower.find(sg) != map_segment_in_shower.end()) continue; + + // Get segment properties + double medium_dQ_dx = segment_median_dQ_dx(sg); + double medium_dQ_dx_1 = medium_dQ_dx / (43e3 / units::cm); + + // Get particle type if available + int particle_type = 0; + if (sg->has_particle_info() && sg->particle_info()) { + particle_type = sg->particle_info()->pdg(); + } + + // Skip segments with certain particle types and high dQ/dx + if ((particle_type == 11) || + (particle_type == 2212 && ((medium_dQ_dx_1 > 1.45 && pair_result.first <= 3) || medium_dQ_dx_1 > 2.7)) || + (particle_type == 211 && medium_dQ_dx_1 > 2.0)) { + continue; + } + + // Create a new shower + ShowerPtr shower = std::make_shared(graph); + shower->set_start_vertex(main_vertex, 1); + shower->set_start_segment(sg); + shower->complete_structure_with_start_segment(used_segments); + + // Analyze shower structure + int n_tracks = 0; + int n_showers = 0; + double total_length = 0; + double max_seg_length = 0; + SegmentPtr max_sg = nullptr; + bool flag_good_track = false; + + // Iterate through shower segments + for (auto edesc : shower->edges()) { + auto sg1 = shower->view_graph()[edesc].segment; + if (!sg1) continue; + + double length = segment_track_length(sg1); + double medium_dQ_dx_sg = segment_median_dQ_dx(sg1); + double medium_dQ_dx_norm = medium_dQ_dx_sg / (43e3 / units::cm); + + // Check if segment is a shower + bool is_shower = sg1->flags_any(SegmentFlags::kShowerTrajectory) || + sg1->flags_any(SegmentFlags::kShowerTopology); + if (is_shower) n_showers++; + + n_tracks++; + total_length += length; + + if (max_seg_length < length) { + max_seg_length = length; + max_sg = sg1; + } + + // Check for good track properties + if (!sg1->dir_weak() && (length > 3.6 * units::cm || (length > 2.4 * units::cm && medium_dQ_dx_norm > 2.5))) { + // Find end vertex of this segment + auto seg_edesc = sg1->get_descriptor(); + auto source_vdesc = boost::source(seg_edesc, graph); + auto target_vdesc = boost::target(seg_edesc, graph); + VertexPtr v1 = graph[source_vdesc].vertex; + VertexPtr v2 = graph[target_vdesc].vertex; + + // Determine end vertex based on dirsign + // For dirsign == 1, direction is from front to back of fits + // For dirsign == -1, direction is from back to front of fits + VertexPtr end_vertex = nullptr; + if (sg1->dirsign() == 1) { + // End is at back of fits + if (!sg1->fits().empty() && v1 && v2) { + auto& fits = sg1->fits(); + double dist1 = (fits.back().point - (v1->fit().valid() ? v1->fit().point : v1->wcpt().point)).magnitude(); + double dist2 = (fits.back().point - (v2->fit().valid() ? v2->fit().point : v2->wcpt().point)).magnitude(); + end_vertex = (dist1 < dist2) ? v1 : v2; + } + } else if (sg1->dirsign() == -1) { + // End is at front of fits (reversed direction) + if (!sg1->fits().empty() && v1 && v2) { + auto& fits = sg1->fits(); + double dist1 = (fits.front().point - (v1->fit().valid() ? v1->fit().point : v1->wcpt().point)).magnitude(); + double dist2 = (fits.front().point - (v2->fit().valid() ? v2->fit().point : v2->wcpt().point)).magnitude(); + end_vertex = (dist1 < dist2) ? v1 : v2; + } + } + + if (end_vertex && map_vertex_segments.find(end_vertex) != map_vertex_segments.end()) { + if (map_vertex_segments[end_vertex].size() > 1) { + bool flag_non_ele = false; + for (auto sg2 : map_vertex_segments[end_vertex]) { + if (sg2 == sg1) continue; + bool is_shower2 = sg2->flags_any(SegmentFlags::kShowerTrajectory) || + sg2->flags_any(SegmentFlags::kShowerTopology); + if (!is_shower2) flag_non_ele = true; + } + + if (!flag_non_ele && map_vertex_segments[end_vertex].size() <= 3) { + flag_good_track = true; + } + } else { + flag_good_track = true; + } + } + } + } + + (void) n_showers; // n_showers is not used further + + // Count vertex types in shower + int n_multi_vtx = 0; + int n_two_vtx = 0; + std::map vtx_segment_count; + + for (auto edesc : shower->edges()) { + auto seg = shower->view_graph()[edesc].segment; + if (!seg) continue; + + auto seg_edesc = seg->get_descriptor(); + auto source_vdesc = boost::source(seg_edesc, graph); + auto target_vdesc = boost::target(seg_edesc, graph); + VertexPtr v1 = graph[source_vdesc].vertex; + VertexPtr v2 = graph[target_vdesc].vertex; + + if (v1) vtx_segment_count[v1]++; + if (v2) vtx_segment_count[v2]++; + } + + for (auto& [vtx, count] : vtx_segment_count) { + if (count == 2) n_two_vtx++; + else if (count > 2) n_multi_vtx++; + } + + // Apply selection criteria + if (!flag_good_track && n_multi_vtx > 0 && max_seg_length < 65 * units::cm && + ((total_length < n_tracks * 27 * units::cm && total_length < 85 * units::cm) || + (total_length < n_tracks * 18 * units::cm && total_length < 95 * units::cm)) && + n_two_vtx < 3) { + + map_shower_max_sg[shower] = max_sg; + if (shower->get_total_length() > max_length) { + max_shower = shower; + max_length = shower->get_total_length(); + } + } + } + + // Process selected showers + for (auto& [shower, max_sg] : map_shower_max_sg) { + if (shower == max_shower) { + // Convert to EM shower (particle type 11 = electron) + std::cout << "Convert EM shower " << shower->start_segment()->id() << std::endl; + if (shower->start_segment() && shower->start_segment()->has_particle_info() && shower->start_segment()->particle_info()) { + shower->start_segment()->particle_info()->set_pdg(11); + } + + // Set avoid muon check flag on max segment + if (max_sg) { + max_sg->set_flags(SegmentFlags::kAvoidMuonCheck); + } + + // Add shower to collection + showers.insert(shower); + + // Check for showers to delete that conflict with new shower + // Collect all vertices in the new shower + std::set shower_vertices; + for (auto vdesc : shower->nodes()) { + auto vtx = shower->view_graph()[vdesc].vertex; + if (vtx) shower_vertices.insert(vtx); + } + + for (auto shower1 : showers) { + if (shower == shower1) continue; + auto [start_vtx1, conn_type1] = shower1->get_start_vertex_and_type(); + if (conn_type1 == 1 && start_vtx1 && shower_vertices.find(start_vtx1) != shower_vertices.end()) { + del_showers.insert(shower1); + } + } + } + } + + // Delete conflicting showers + for (auto shower1 : del_showers) { + auto [start_vtx1, conn_type1] = shower1->get_start_vertex_and_type(); + if (start_vtx1 != main_vertex) { + showers.erase(shower1); + } + } + + // Update shower maps + update_shower_maps(showers, map_vertex_in_shower, map_segment_in_shower, map_vertex_to_shower, used_shower_clusters); + } +} + +void PatternAlgorithms::shower_clustering_with_nv_from_main_cluster(Graph& graph, VertexPtr main_vertex, Facade::Cluster* main_cluster, std::set& showers, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters){ + if (!main_vertex || !main_cluster) return; + + // Build map_segment_vertices from graph + std::map> map_segment_vertices; + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr seg = graph[*eit].segment; + if (!seg) continue; + + auto source_vdesc = boost::source(*eit, graph); + auto target_vdesc = boost::target(*eit, graph); + VertexPtr v1 = graph[source_vdesc].vertex; + VertexPtr v2 = graph[target_vdesc].vertex; + + if (v1) map_segment_vertices[seg].insert(v1); + if (v2) map_segment_vertices[seg].insert(v2); + } + + std::map map_shower_dir; + std::map map_shower_angle_offset; + WireCell::Vector drift_dir(1, 0, 0); + SegmentPtr max_length_segment = nullptr; + + // Step 1: Find the maximum length segment in main_cluster + { + double max_length = 0; + for (auto& [seg, vertices] : map_segment_vertices) { + if (seg->cluster() != main_cluster) continue; + double length = segment_track_length(seg); + if (length > max_length && length > 6 * units::cm) { + max_length = length; + max_length_segment = seg; + } + } + } + + // Step 2: Build map_shower_dir for showers in main_cluster + for (auto& [seg, vertices] : map_segment_vertices) { + if (seg->cluster() != main_cluster) continue; + if (map_segment_in_shower.find(seg) == map_segment_in_shower.end()) continue; + + ShowerPtr shower = map_segment_in_shower[seg]; + + // Skip long muons + int particle_type = 0; + if (shower->start_segment() && shower->start_segment()->has_particle_info() && shower->start_segment()->particle_info()) { + particle_type = shower->start_segment()->particle_info()->pdg(); + } + if (std::abs(particle_type) == 13) continue; + + double total_length = shower->get_total_length(); + + if (seg == shower->start_segment()) { + auto [start_vtx, conn_type] = shower->get_start_vertex_and_type(); + WireCell::Point start_point = start_vtx ? (start_vtx->fit().valid() ? start_vtx->fit().point : start_vtx->wcpt().point) : WireCell::Point(0, 0, 0); + + bool is_shower_topology = seg->flags_any(SegmentFlags::kShowerTopology); + double medium_dQ_dx = segment_median_dQ_dx(seg); + double seg_length = segment_track_length(seg); + + if (is_shower_topology || shower->get_num_segments() > 2 || + (medium_dQ_dx > 43e3 / units::cm * 1.5 && seg_length > 0)) { + if (seg_length > 10 * units::cm) { + WireCell::Vector dir_shower = segment_cal_dir_3vector(seg, start_point, 15 * units::cm); + map_shower_dir[shower] = dir_shower; + } else { + WireCell::Vector dir_shower = shower_cal_dir_3vector(*shower, start_point, 15 * units::cm); + map_shower_dir[shower] = dir_shower; + } + } else if (shower->get_num_segments() <= 2) { + if (total_length > 30 * units::cm) { + if (seg_length > 10 * units::cm) { + WireCell::Vector dir_shower = segment_cal_dir_3vector(seg, start_point, 15 * units::cm); + map_shower_dir[shower] = dir_shower; + } else { + WireCell::Vector dir_shower = shower_cal_dir_3vector(*shower, start_point, 15 * units::cm); + map_shower_dir[shower] = dir_shower; + } + } + } + + // Very large shower + if (total_length > 100 * units::cm) { + WireCell::Vector dir_shower = shower_cal_dir_3vector(*shower, start_point, 60 * units::cm); + map_shower_dir[shower] = dir_shower; + } + + // Max length segment or segment connected to main_vertex + if (seg == max_length_segment && map_shower_dir.find(shower) == map_shower_dir.end()) { + WireCell::Vector dir_shower = shower_cal_dir_3vector(*shower, start_point, 15 * units::cm); + map_shower_dir[shower] = dir_shower; + } else if (map_shower_dir.find(shower) == map_shower_dir.end() && + map_segment_vertices[seg].find(main_vertex) != map_segment_vertices[seg].end()) { + if (seg_length > 5 * units::cm) { + WireCell::Vector dir_shower = shower_cal_dir_3vector(*shower, start_point, 15 * units::cm); + map_shower_dir[shower] = dir_shower; + } + } + + // Check if parallel to drift direction + if (map_shower_dir.find(shower) != map_shower_dir.end()) { + map_shower_angle_offset[shower] = 0; + double angle_to_drift = std::abs(map_shower_dir[shower].dot(drift_dir) / (map_shower_dir[shower].magnitude() * drift_dir.magnitude())); + angle_to_drift = std::acos(std::clamp(angle_to_drift, -1.0, 1.0)) / M_PI * 180.0; + if (std::abs(angle_to_drift - 90) < 5) { + map_shower_dir[shower] = shower_cal_dir_3vector(*shower, start_point, 50 * units::cm); + map_shower_angle_offset[shower] = 5; + } + } + } + } + + // Step 3: If no shower directions found, try to add segments based on closest distance + if (map_shower_dir.empty()) { + std::map map_shower_length; + for (auto shower : showers) { + map_shower_length[shower] = shower->get_total_length(); + } + + bool flag_continue = true; + while (flag_continue) { + flag_continue = false; + for (auto& [seg1, vertices] : map_segment_vertices) { + if (seg1->cluster() == main_cluster) continue; + if (map_segment_in_shower.find(seg1) != map_segment_in_shower.end()) continue; + + double min_dis = 1e9; + ShowerPtr min_shower = nullptr; + + for (auto shower : showers) { + int particle_type = 0; + if (shower->start_segment() && shower->start_segment()->has_particle_info() && shower->start_segment()->particle_info()) { + particle_type = shower->start_segment()->particle_info()->pdg(); + } + if (particle_type == 13) continue; + + if (segment_track_length(seg1) > 0.75 * map_shower_length[shower]) continue; + + double dis = shower_get_closest_dis(*shower, seg1); + if (dis < min_dis) { + min_dis = dis; + min_shower = shower; + } + } + + if (min_shower && min_dis < 3.5 * units::cm) { + min_shower->add_segment(seg1); + map_shower_length[min_shower] = min_shower->get_total_length(); + flag_continue = true; + } + } + update_shower_maps(showers, map_vertex_in_shower, map_segment_in_shower, map_vertex_to_shower, used_shower_clusters); + } + } + + if (map_shower_dir.empty()) return; + + // Step 4: Examine other segments and add to showers based on angle and distance + for (auto& [seg1, vertices] : map_segment_vertices) { + if (seg1->cluster() == main_cluster) continue; + if (map_segment_in_shower.find(seg1) != map_segment_in_shower.end()) continue; + + double min_dis = 1e9; + ShowerPtr min_shower = nullptr; + + for (auto& [shower, dir] : map_shower_dir) { + auto [start_vtx, conn_type] = shower->get_start_vertex_and_type(); + if (!start_vtx) continue; + + WireCell::Point start_point = start_vtx->fit().valid() ? start_vtx->fit().point : start_vtx->wcpt().point; + + // Get closest point on segment to shower start vertex + auto [dist, closest_pt] = segment_get_closest_point(seg1, start_point); + + // Vector from shower start to closest point + WireCell::Vector v1(closest_pt.x() - start_point.x(), + closest_pt.y() - start_point.y(), + closest_pt.z() - start_point.z()); + + double angle = std::acos(std::clamp(dir.dot(v1) / (dir.magnitude() * v1.magnitude()), -1.0, 1.0)); + angle = angle / M_PI * 180.0; + + double angle_offset = 0; + if (map_shower_angle_offset.find(shower) != map_shower_angle_offset.end()) { + angle_offset = map_shower_angle_offset[shower]; + } + + // Check angle and distance criteria + if ((angle < 25.0 + angle_offset && dist < 80 * units::cm) || + (angle < 12.5 + angle_offset * 8 / 5 && dist < 130 * units::cm) || + (angle < 5 + angle_offset * 2 && dist < 200 * units::cm)) { + + double dis = std::pow(dist * std::cos(angle * M_PI / 180.0), 2) / std::pow(40 * units::cm, 2) + + std::pow(dist * std::sin(angle * M_PI / 180.0), 2) / std::pow(5 * units::cm, 2); + + if (dis < min_dis) { + min_dis = dis; + min_shower = shower; + } + } + } + + if (min_shower) { + min_shower->add_segment(seg1); + } + } + + update_shower_maps(showers, map_vertex_in_shower, map_segment_in_shower, map_vertex_to_shower, used_shower_clusters); +} + +void PatternAlgorithms::shower_clustering_with_nv_from_vertices(Graph& graph, VertexPtr main_vertex, Facade::Cluster* main_cluster, std::vector& other_clusters, std::set& showers, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model){ + if (!main_vertex || !main_cluster) return; + + // Build map_cluster_segments and map_segment_cluster + std::map> map_cluster_segments; + std::map map_segment_cluster; + std::map> map_segment_vertices; + + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr seg = graph[*eit].segment; + if (!seg || !seg->cluster()) continue; + + map_cluster_segments[seg->cluster()].push_back(seg); + map_segment_cluster[seg] = seg->cluster(); + + auto source_vdesc = boost::source(*eit, graph); + auto target_vdesc = boost::target(*eit, graph); + VertexPtr v1 = graph[source_vdesc].vertex; + VertexPtr v2 = graph[target_vdesc].vertex; + + if (v1) map_segment_vertices[seg].insert(v1); + if (v2) map_segment_vertices[seg].insert(v2); + } + + // Step 1: Build map_cluster_center_point + std::map> map_cluster_center_point; + + for (auto cluster : other_clusters) { + auto it1 = map_cluster_segments.find(cluster); + if (it1 == map_cluster_segments.end()) continue; + + double acc_length = 0; + double acc_length1 = 0; + WireCell::Point p(0, 0, 0); + int np = 0; + + for (auto seg : it1->second) { + if (map_segment_in_shower.find(seg) != map_segment_in_shower.end()) continue; + + bool is_shower = seg->flags_any(SegmentFlags::kShowerTrajectory) || seg->flags_any(SegmentFlags::kShowerTopology); + int particle_type = 0; + if (seg->has_particle_info() && seg->particle_info()) { + particle_type = seg->particle_info()->pdg(); + } + + if (is_shower || particle_type == 0 || + ((std::abs(particle_type) == 13 || std::abs(particle_type) == 211) && seg->dir_weak())) { + double length = segment_track_length(seg); + acc_length += length; + + const auto& fits = seg->fits(); + for (const auto& fit : fits) { + p.set(p.x() + fit.point.x(), p.y() + fit.point.y(), p.z() + fit.point.z()); + np++; + } + } + + if (particle_type != 11 && !is_shower) { + acc_length1 += segment_track_length(seg); + } + } + + if ((acc_length > 1.0 * units::cm && acc_length >= acc_length1) || acc_length > 10 * units::cm) { + if (np > 0) { + p.set(p.x() / np, p.y() / np, p.z() / np); + } + map_cluster_center_point[cluster] = std::make_pair(p, acc_length); + } + } + + // Step 2: List main cluster vertices + std::vector main_cluster_vertices; + auto [vbegin, vend] = boost::vertices(graph); + for (auto vit = vbegin; vit != vend; ++vit) { + VertexPtr vtx = graph[*vit].vertex; + if (!vtx || !vtx->cluster() || vtx->cluster() != main_cluster) continue; + + if (vtx != main_vertex) { + if (vertices_in_long_muon.find(vtx) != vertices_in_long_muon.end()) continue; + if (map_vertex_in_shower.find(vtx) != map_vertex_in_shower.end()) continue; + } + main_cluster_vertices.push_back(vtx); + } + + // Step 3: Analyze each cluster against main cluster vertices + std::map map_cluster_pi; + + // Get wpid_params for point cloud creation + auto* grouping = main_cluster->grouping(); + if (!grouping) return; + const auto& wpids = grouping->wpids(); + std::map> wpid_params; + std::map> wpid_U_dir, wpid_V_dir, wpid_W_dir; + std::set apas; + Facade::compute_wireplane_params(wpids, dv, wpid_params, wpid_U_dir, wpid_V_dir, wpid_W_dir, apas); + + for (auto& [cluster, center_pair] : map_cluster_center_point) { + WireCell::Point center_p = center_pair.first; + + // Create point cloud from cluster segments + std::vector> point_plane_pairs; + for (auto seg : map_cluster_segments[cluster]) { + for (const auto& fit : seg->fits()) { + WirePlaneId wpid = dv->contained_by(fit.point); + point_plane_pairs.emplace_back(fit.point, wpid); + } + } + auto dpc_points = Facade::make_points_direct(cluster, dv, wpid_params, point_plane_pairs, false); + auto pcloud = std::make_shared(wpid_params); + pcloud->add_points(dpc_points); + + cluster_point_info min_pi; + min_pi.cluster = cluster; + min_pi.min_angle = 90; + min_pi.min_dis = 1e9; + min_pi.min_vertex = nullptr; + + cluster_point_info main_pi; + main_pi.cluster = cluster; + main_pi.min_vertex = main_vertex; + + for (auto vtx : main_cluster_vertices) { + WireCell::Point vtx_pt = vtx->fit().valid() ? vtx->fit().point : vtx->wcpt().point; + + // Get closest point using KD-tree + auto& kd3d = pcloud->kd3d(); + std::vector query = {vtx_pt.x(), vtx_pt.y(), vtx_pt.z()}; + auto results = kd3d.knn(1, query); + + if (results.empty()) continue; + + const size_t idx = results[0].first; + const double dis = std::sqrt(results[0].second); // KD-tree returns squared distance + const auto& closest_pt_data = pcloud->get_points()[idx]; + WireCell::Point closest_pt(closest_pt_data.x, closest_pt_data.y, closest_pt_data.z); + + WireCell::Vector v1(closest_pt.x() - vtx_pt.x(), + closest_pt.y() - vtx_pt.y(), + closest_pt.z() - vtx_pt.z()); + WireCell::Vector v2(center_p.x() - closest_pt.x(), + center_p.y() - closest_pt.y(), + center_p.z() - closest_pt.z()); + + WireCell::Point near_center = pcloud->get_center_point_radius(closest_pt, 2 * units::cm); + WireCell::Vector v3(near_center.x() - closest_pt.x(), + near_center.y() - closest_pt.y(), + near_center.z() - closest_pt.z()); + + double angle = std::acos(std::clamp(v1.dot(v2) / (v1.magnitude() * v2.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + double angle3 = std::acos(std::clamp(v1.dot(v3) / (v1.magnitude() * v3.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + + if (angle < 30 || (dis < 5 * units::cm && angle < 45)) { + angle = std::min(angle, angle3); + } + + if (angle < 7.5) { + if (dis * std::sin(angle / 180.0 * M_PI) < min_pi.min_dis * std::sin(min_pi.min_angle / 180.0 * M_PI) && angle < 90) { + min_pi.min_angle = angle; + min_pi.min_dis = dis; + min_pi.min_vertex = vtx; + min_pi.min_point = closest_pt; + } + } else { + if (angle < min_pi.min_angle) { + min_pi.min_angle = angle; + min_pi.min_dis = dis; + min_pi.min_vertex = vtx; + min_pi.min_point = closest_pt; + } + } + + if (vtx == main_vertex) { + main_pi.min_angle = angle; + main_pi.min_dis = dis; + main_pi.min_point = closest_pt; + } + } + + if (!min_pi.min_vertex) { + min_pi.min_angle = main_pi.min_angle; + min_pi.min_vertex = main_vertex; + min_pi.min_point = main_pi.min_point; + min_pi.min_dis = main_pi.min_dis; + } + + WireCell::Point main_vtx_pt = main_vertex->fit().valid() ? main_vertex->fit().point : main_vertex->wcpt().point; + WireCell::Point min_vtx_pt = min_pi.min_vertex->fit().valid() ? min_pi.min_vertex->fit().point : min_pi.min_vertex->wcpt().point; + double vtx_dis = (main_vtx_pt - min_vtx_pt).magnitude(); + + if (main_pi.min_angle < min_pi.min_angle + 3 && main_pi.min_dis < min_pi.min_dis * 1.2 && + (min_pi.min_angle > 0.9 * main_pi.min_angle || vtx_dis < 1.5 * units::cm)) { + map_cluster_pi[cluster] = main_pi; + } else { + map_cluster_pi[cluster] = min_pi; + } + } + + // Step 4: Sort by distance + std::vector vec_pi; + for (auto& [cluster, pi] : map_cluster_pi) { + vec_pi.push_back(pi); + } + std::sort(vec_pi.begin(), vec_pi.end(), sortbydis); + + std::map map_cluster_associated_vertex; + for (const auto& pi : vec_pi) { + if (pi.min_angle < 10) { + map_cluster_associated_vertex[pi.cluster] = pi.min_vertex; + } + } + + // Step 5: Process each cluster + for (const auto& pi : vec_pi) { + Facade::Cluster* cluster = pi.cluster; + VertexPtr vertex = pi.min_vertex; + WireCell::Point point = pi.min_point; + SegmentPtr sg1 = nullptr; + double angle = pi.min_angle; + + if (angle > 50 && pi.min_dis > 6 * units::cm) continue; + if (angle > 60) continue; + + // Find segment at the point + for (auto seg : map_cluster_segments[cluster]) { + if (map_segment_in_shower.find(seg) != map_segment_in_shower.end()) continue; + auto [dis, closest_pt] = segment_get_closest_point(seg, point); + if (dis < 0.01 * units::cm) { + sg1 = seg; + break; + } + } + + if (!sg1) continue; + + // Create new shower + ShowerPtr shower = std::make_shared(graph); + shower->set_start_vertex(vertex, 2); + showers.insert(shower); + + // Check if point is at segment endpoint + const auto& fits = sg1->fits(); + if (!fits.empty()) { + double dist_front = (fits.front().point - point).magnitude(); + double dist_back = (fits.back().point - point).magnitude(); + + if (dist_front < 0.01 * units::cm || dist_back < 0.01 * units::cm) { + shower->set_start_segment(sg1, true); + } else { + // Break segment at point + auto [success, seg_pair, new_vtx] = break_segment(graph, sg1, point, particle_data, recomb_model, dv); + + if (!success || !new_vtx) { + shower->set_start_segment(sg1); + } else { + // Determine which segment to use based on direction to vertex + WireCell::Point vtx_pt = vertex->fit().valid() ? vertex->fit().point : vertex->wcpt().point; + WireCell::Vector v3(point.x() - vtx_pt.x(), point.y() - vtx_pt.y(), point.z() - vtx_pt.z()); + + WireCell::Vector v1 = segment_cal_dir_3vector(seg_pair.first, point, 5 * units::cm); + WireCell::Vector v2 = segment_cal_dir_3vector(seg_pair.second, point, 5 * units::cm); + + double angle1 = std::acos(std::clamp(v1.dot(v3) / (v1.magnitude() * v3.magnitude()), -1.0, 1.0)); + double angle2 = std::acos(std::clamp(v2.dot(v3) / (v2.magnitude() * v3.magnitude()), -1.0, 1.0)); + + if (angle1 < angle2) { + shower->set_start_segment(seg_pair.first); + } else { + shower->set_start_segment(seg_pair.second); + } + } + } + } else { + shower->set_start_segment(sg1); + } + + // Set direction based on vertex proximity + auto start_seg = shower->start_segment(); + if (start_seg) { + const auto& seg_fits = start_seg->fits(); + if (!seg_fits.empty()) { + WireCell::Point vtx_pt = vertex->fit().valid() ? vertex->fit().point : vertex->wcpt().point; + double dis1 = (vtx_pt - seg_fits.front().point).magnitude(); + double dis2 = (vtx_pt - seg_fits.back().point).magnitude(); + + if (dis1 < dis2) { + start_seg->dirsign(1); + } else { + start_seg->dirsign(-1); + } + } + + // Set particle type to electron if needed + int pdg = 0; + if (start_seg->has_particle_info() && start_seg->particle_info()) { + pdg = start_seg->particle_info()->pdg(); + } + if (pdg == 0 || std::abs(pdg) == 13) { + auto four_momentum = segment_cal_4mom(start_seg, 11, particle_data, recomb_model); + + // Create ParticleInfo for electron + auto pinfo = std::make_shared( + 11, // electron PDG + particle_data->get_particle_mass(11), // electron mass + particle_data->pdg_to_name(11), // "electron" + four_momentum // 4-momentum + ); + + // Store particle info in start_segment + start_seg->particle_info(pinfo); + } + } + + // Complete shower structure + std::set used_segments; + shower->complete_structure_with_start_segment(used_segments); + + // Calculate shower direction + auto [start_vtx, conn_type] = shower->get_start_vertex_and_type(); + WireCell::Point start_pt = start_vtx ? (start_vtx->fit().valid() ? start_vtx->fit().point : start_vtx->wcpt().point) : point; + + WireCell::Vector dir_shower = segment_cal_dir_3vector(shower->start_segment(), point, 15 * units::cm); + WireCell::Vector dir_main(point.x() - start_pt.x(), point.y() - start_pt.y(), point.z() - start_pt.z()); + + if (std::acos(std::clamp(dir_shower.dot(dir_main) / (dir_shower.magnitude() * dir_main.magnitude()), -1.0, 1.0)) / M_PI * 180.0 > 30) { + auto [_, test_p] = shower_get_closest_point(*shower, start_pt); + dir_shower = shower_cal_dir_3vector(*shower, test_p, 30 * units::cm); + } + if (dir_shower.magnitude() < 0.001) dir_shower = dir_main; + + // Add segments from other clusters + for (auto& [seg1, vertices] : map_segment_vertices) { + if (seg1->cluster() == main_cluster) continue; + if (map_segment_in_shower.find(seg1) != map_segment_in_shower.end()) continue; + if (seg1->cluster() == shower->start_segment()->cluster()) continue; + + auto it1 = map_cluster_associated_vertex.find(map_segment_cluster[seg1]); + + auto [pair_dis, pair_point] = segment_get_closest_point(seg1, start_pt); + WireCell::Vector v1(pair_point.x() - start_pt.x(), pair_point.y() - start_pt.y(), pair_point.z() - start_pt.z()); + WireCell::Vector v2(pair_point.x() - point.x(), pair_point.y() - point.y(), pair_point.z() - point.z()); + + double angle_v1 = std::acos(std::clamp(dir_shower.dot(v1) / (dir_shower.magnitude() * v1.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + double angle_v2 = std::acos(std::clamp(dir_shower.dot(v2) / (dir_shower.magnitude() * v2.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + + const auto& seg1_fits = seg1->fits(); + double tmp_shower_dis = !seg1_fits.empty() ? (shower->start_segment()->fits().front().point - seg1_fits.front().point).magnitude() : 1e9; + double close_shower_dis = shower_get_closest_dis(*shower, seg1); + + if (angle_v2 > 30) continue; + + if ((angle_v1 < 25 && (pair_dis < 80 * units::cm || close_shower_dis < 25 * units::cm)) || + (angle_v2 < 25 && (tmp_shower_dis < 40 * units::cm || close_shower_dis < 25 * units::cm)) || + (angle_v1 < 12.5 && (pair_dis < 120 * units::cm || close_shower_dis < 40 * units::cm)) || + (angle_v2 < 12.5 && (tmp_shower_dis < 80 * units::cm || close_shower_dis < 40 * units::cm))) { + + if (it1 != map_cluster_associated_vertex.end() && seg1->cluster() != shower->start_segment()->cluster()) { + if (it1->second != vertex) { + double dis1 = shower_get_dis(*shower, seg1); + if (dis1 > 25 * units::cm && dis1 > pair_dis * 0.4) { + continue; + } + } + } + shower->add_segment(seg1); + } + } + + // Update particle type + shower->update_particle_type(particle_data, recomb_model); + + bool tmp_flag = (shower->start_vertex() == main_vertex); + std::cout << "Separated shower: " << shower->start_segment()->cluster()->get_cluster_id() * 1000 + shower->start_segment()->id() + << " " << (shower->start_segment()->has_particle_info() && shower->start_segment()->particle_info() ? shower->start_segment()->particle_info()->pdg() : 0) + << " " << shower->get_num_segments() << " " << tmp_flag << " " << pi.min_dis / units::cm << std::endl; + + update_shower_maps(showers, map_vertex_in_shower, map_segment_in_shower, map_vertex_to_shower, used_shower_clusters); + + // Iteratively add segments based on distance + { + std::map map_shower_length; + std::map map_shower_dir; + + for (auto shower1 : showers) { + map_shower_length[shower1] = shower1->get_total_length(); + auto [start_vtx1, _] = shower1->get_start_vertex_and_type(); + WireCell::Point start_pt1 = start_vtx1 ? (start_vtx1->fit().valid() ? start_vtx1->fit().point : start_vtx1->wcpt().point) : WireCell::Point(0, 0, 0); + auto [__, test_p] = shower_get_closest_point(*shower1, start_pt1); + map_shower_dir[shower1] = shower_cal_dir_3vector(*shower1, test_p, 30 * units::cm); + } + + bool flag_continue = true; + while (flag_continue) { + flag_continue = false; + for (auto& [seg1, vertices] : map_segment_vertices) { + if (seg1->cluster() == main_cluster) continue; + if (map_segment_in_shower.find(seg1) != map_segment_in_shower.end()) continue; + + double min_dis = 1e9; + ShowerPtr min_shower = nullptr; + + for (auto shower1 : showers) { + if (segment_track_length(seg1) > 0.75 * map_shower_length[shower1]) continue; + + auto [start_vtx1, _] = shower1->get_start_vertex_and_type(); + WireCell::Point start_point1 = start_vtx1 ? (start_vtx1->fit().valid() ? start_vtx1->fit().point : start_vtx1->wcpt().point) : WireCell::Point(0, 0, 0); + auto [__, test_p] = segment_get_closest_point(seg1, start_point1); + auto [___, test_p1] = shower_get_closest_point(*shower1, start_point1); + + WireCell::Vector tmp_dir(test_p.x() - test_p1.x(), test_p.y() - test_p1.y(), test_p.z() - test_p1.z()); + double angle = std::acos(std::clamp(tmp_dir.dot(map_shower_dir[shower1]) / (tmp_dir.magnitude() * map_shower_dir[shower1].magnitude()), -1.0, 1.0)) / M_PI * 180.0; + + double dis = shower_get_closest_dis(*shower1, seg1); + if (dis < min_dis && angle < 45) { + min_dis = dis; + min_shower = shower1; + } + } + + if (min_shower && min_dis < 3.5 * units::cm) { + min_shower->add_segment(seg1); + map_shower_length[min_shower] = min_shower->get_total_length(); + flag_continue = true; + } + } + update_shower_maps(showers, map_vertex_in_shower, map_segment_in_shower, map_vertex_to_shower, used_shower_clusters); + } + } + } + + std::cout << "With separated-cluster shower: " << showers.size() << std::endl; +} + +void PatternAlgorithms::examine_merge_showers(std::set& showers, VertexPtr main_vertex, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, Graph& graph, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model){ + + if (!main_vertex || map_vertex_to_shower.find(main_vertex) == map_vertex_to_shower.end()) { + return; + } + + // Get all showers starting at main vertex + auto& main_vertex_showers = map_vertex_to_shower[main_vertex]; + + // Track which showers have been merged + std::set showers_to_remove; + bool flag_update = false; + + // Iterate through all showers from main vertex + for (auto shower1 : main_vertex_showers) { + // Skip if already processed + if (showers_to_remove.find(shower1) != showers_to_remove.end()) continue; + + // Skip muons (particle type 13) + if (shower1->get_particle_type() == 13) continue; + + // Check start vertex type + auto [start_vtx1, start_type1] = shower1->get_start_vertex_and_type(); + if (start_type1 != 1) continue; + + // Calculate direction for shower1 (100 cm distance cut) + WireCell::Point start_point1 = shower1->get_start_point(); + WireCell::Vector dir1 = shower_cal_dir_3vector(*shower1, start_point1, 100 * units::cm); + + // Look for candidate showers to merge + for (auto shower2 : main_vertex_showers) { + // Skip if same shower + if (shower1 == shower2) continue; + + // Skip if already processed + if (showers_to_remove.find(shower2) != showers_to_remove.end()) continue; + + // Skip muons + if (shower2->get_particle_type() == 13) continue; + + // Check start vertex type + auto [start_vtx2, start_type2] = shower2->get_start_vertex_and_type(); + if (start_type2 != 2) continue; + + // Calculate direction for shower2 + WireCell::Point start_point2 = shower2->get_start_point(); + WireCell::Vector dir2 = shower_cal_dir_3vector(*shower2, start_point2, 100 * units::cm); + + // Calculate angle between directions + double cos_angle = dir1.dot(dir2) / (dir1.magnitude() * dir2.magnitude()); + double angle = std::acos(std::max(-1.0, std::min(1.0, cos_angle))); + double angle_deg = angle * 180.0 / M_PI; + + // Merge if angle is less than 10 degrees + if (angle_deg < 10.0) { + // Merge shower2 into shower1 + shower1->add_shower(*shower2); + + // Update particle type and kinematics + shower1->update_particle_type(particle_data, recomb_model); + shower1->calculate_kinematics(particle_data, recomb_model); + + // Recalculate kinetic charge + double kine_charge = cal_kine_charge(shower1, graph, track_fitter, dv); + shower1->set_kine_charge(kine_charge); + shower1->set_flag_kinematics(true); + + // Mark shower2 for removal + showers_to_remove.insert(shower2); + flag_update = true; + } + } + } + + // Remove merged showers from the main set + for (auto shower : showers_to_remove) { + showers.erase(shower); + } + + // Update shower maps if any merges occurred + if (flag_update) { + update_shower_maps(showers, map_vertex_in_shower, map_segment_in_shower, + map_vertex_to_shower, used_shower_clusters); + } +} + + +void PatternAlgorithms::shower_clustering_in_other_clusters(Graph& graph, VertexPtr main_vertex, std::set& showers, Facade::Cluster* main_cluster, std::vector& other_clusters, std::map map_cluster_main_vertices, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model, bool flag_save){ + + if (!main_vertex || !main_cluster) return; + + // Build map_vertex_segments and map_segment_vertices + std::map> map_vertex_segments; + std::map> map_segment_vertices; + + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr seg = graph[*eit].segment; + if (!seg) continue; + + auto source_vdesc = boost::source(*eit, graph); + auto target_vdesc = boost::target(*eit, graph); + VertexPtr v1 = graph[source_vdesc].vertex; + VertexPtr v2 = graph[target_vdesc].vertex; + + if (v1) { + map_vertex_segments[v1].insert(seg); + map_segment_vertices[seg].insert(v1); + } + if (v2) { + map_vertex_segments[v2].insert(seg); + map_segment_vertices[seg].insert(v2); + } + } + + // Build map_cluster_length + std::map map_cluster_length; + for (auto cluster : other_clusters) { + map_cluster_length[cluster] = cluster->get_length(); + } + + // Collect vertices in main cluster as well as existing showers + std::vector vertices; + for (auto& [vtx, seg_set] : map_vertex_segments) { + if ((vtx->cluster() && vtx->cluster()->get_cluster_id() == main_cluster->get_cluster_id()) || + map_vertex_in_shower.find(vtx) != map_vertex_in_shower.end()) { + vertices.push_back(vtx); + } + } + + // Process clusters in map_cluster_main_vertices + for (auto& [cluster, vertex] : map_cluster_main_vertices) { + if (used_shower_clusters.find(cluster) != used_shower_clusters.end()) continue; + if (map_cluster_length[cluster] < 4 * units::cm) continue; + + double min_dis = 1e9; + VertexPtr min_vertex = nullptr; + double main_dis = 1e9; + + for (auto vtx : vertices) { + WireCell::Point vtx_pt = vtx->fit().valid() ? vtx->fit().point : vtx->wcpt().point; + + // Get closest distance using cluster's get_closest_dis method + double dis = cluster->get_closest_dis(vtx_pt); + + if (dis < min_dis) { + min_dis = dis; + min_vertex = vtx; + } + + if (vtx == main_vertex) { + main_dis = dis; + } + } + + if (min_dis > 0.8 * main_dis) { + min_dis = main_dis; + min_vertex = main_vertex; + } + + // Find a shower segment starting at vertex + SegmentPtr sg = nullptr; + if (map_vertex_segments.find(vertex) != map_vertex_segments.end()) { + for (auto seg : map_vertex_segments[vertex]) { + if (seg->flags_any(SegmentFlags::kShowerTrajectory) || + seg->flags_any(SegmentFlags::kShowerTopology)) { + sg = seg; + break; + } + } + } + + int connection_type = 3; + + if (sg) { + // Create new shower + ShowerPtr shower = std::make_shared(graph); + shower->set_start_vertex(min_vertex, connection_type); + shower->set_start_segment(sg); + + // Set direction based on distance to vertex + WireCell::Point vertex_pt = vertex->fit().valid() ? vertex->fit().point : vertex->wcpt().point; + const auto& fits = sg->fits(); + if (!fits.empty()) { + double dis1 = (vertex_pt - fits.front().point).magnitude(); + double dis2 = (vertex_pt - fits.back().point).magnitude(); + + if (dis1 < dis2) { + sg->dirsign(1); + } else { + sg->dirsign(-1); + } + } + + // Complete shower structure + std::set used_segments; + shower->complete_structure_with_start_segment(used_segments); + + // Calculate shower direction + WireCell::Vector dir_shower = shower_cal_dir_3vector(*shower, vertex_pt, 15 * units::cm); + + // Cluster with the rest - add segments based on angle and distance + for (auto& [seg1, vertices_set] : map_segment_vertices) { + if (seg1->cluster() == main_cluster) continue; + if (map_segment_in_shower.find(seg1) != map_segment_in_shower.end()) continue; + if (seg1->cluster() == shower->start_segment()->cluster()) continue; + + // Find the closest point + auto [pair_dis, pair_point] = segment_get_closest_point(seg1, vertex_pt); + WireCell::Vector v1(pair_point.x() - vertex_pt.x(), + pair_point.y() - vertex_pt.y(), + pair_point.z() - vertex_pt.z()); + + double angle = std::acos(std::clamp(dir_shower.dot(v1) / (dir_shower.magnitude() * v1.magnitude()), -1.0, 1.0)); + angle = angle / M_PI * 180.0; + + if ((angle < 25 && pair_dis < 80 * units::cm) || + (angle < 12.5 && pair_dis < 120 * units::cm)) { + shower->add_segment(seg1); + } + } + + // Update particle type + shower->update_particle_type(particle_data, recomb_model); + + // Check with other showers and merge if needed + std::vector showers_to_be_removed; + for (auto shower1 : showers) { + WireCell::Point start_pt1 = shower1->get_start_point(); + WireCell::Vector dir_shower1 = shower_cal_dir_3vector(*shower1, start_pt1, 15 * units::cm); + WireCell::Vector dir2(start_pt1.x() - vertex_pt.x(), + start_pt1.y() - vertex_pt.y(), + start_pt1.z() - vertex_pt.z()); + + double angle = std::acos(std::clamp(dir_shower.dot(dir_shower1) / (dir_shower.magnitude() * dir_shower1.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + double angle1 = std::acos(std::clamp(dir_shower.dot(dir2) / (dir_shower.magnitude() * dir2.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + + if ((angle < 25 && angle1 < 15 && dir2.magnitude() < 80 * units::cm) || + (angle < 12.5 && angle1 < 7.5 && dir2.magnitude() < 120 * units::cm)) { + shower->add_shower(*shower1); + showers_to_be_removed.push_back(shower1); + } + } + + for (auto shower_to_remove : showers_to_be_removed) { + showers.erase(shower_to_remove); + } + + showers.insert(shower); + } + } + + update_shower_maps(showers, map_vertex_in_shower, map_segment_in_shower, + map_vertex_to_shower, used_shower_clusters); + + // Process remaining other_clusters not in map_cluster_main_vertices + for (auto cluster : other_clusters) { + if (used_shower_clusters.find(cluster) != used_shower_clusters.end()) continue; + + double min_dis = 1e9; + VertexPtr min_vertex = nullptr; + double main_dis = 1e9; + + for (auto vtx : vertices) { + WireCell::Point vtx_pt = vtx->fit().valid() ? vtx->fit().point : vtx->wcpt().point; + + // Get closest distance using cluster's get_closest_dis method + double dis = cluster->get_closest_dis(vtx_pt); + + if (dis < min_dis) { + min_dis = dis; + min_vertex = vtx; + } + + if (vtx == main_vertex) { + main_dis = dis; + } + } + + if (min_dis > 0.8 * main_dis) { + min_dis = main_dis; + min_vertex = main_vertex; + } + + // Find a segment from this cluster + SegmentPtr sg = nullptr; + for (auto& [seg, vertices_set] : map_segment_vertices) { + if (seg->cluster() != cluster) continue; + sg = seg; + break; + } + + int connection_type = 3; + if (min_dis > 80 * units::cm) { + connection_type = 4; + } + + if (!flag_save) connection_type = 4; + + if (sg) { + // Create new shower + ShowerPtr shower = std::make_shared(graph); + shower->set_start_vertex(min_vertex, connection_type); + shower->set_start_segment(sg); + + // Set direction if not already set + if (sg->dirsign() == 0) { + // Get start and end vertices + auto seg_edesc = sg->get_descriptor(); + auto source_vdesc = boost::source(seg_edesc, graph); + auto target_vdesc = boost::target(seg_edesc, graph); + VertexPtr v1 = graph[source_vdesc].vertex; + VertexPtr v2 = graph[target_vdesc].vertex; + + if (v1 && v2) { + if (map_vertex_segments[v1].size() == 1 && map_vertex_segments[v2].size() > 1) { + sg->dirsign(1); + } else if (map_vertex_segments[v1].size() > 1 && map_vertex_segments[v2].size() == 1) { + sg->dirsign(-1); + } else { + // Examine vertices based on distance to main_vertex + WireCell::Point main_vtx_pt = main_vertex->fit().valid() ? main_vertex->fit().point : main_vertex->wcpt().point; + const auto& fits = sg->fits(); + if (!fits.empty()) { + double dis1 = (main_vtx_pt - fits.front().point).magnitude(); + double dis2 = (main_vtx_pt - fits.back().point).magnitude(); + + if (dis1 < dis2) { + sg->dirsign(1); + } else { + sg->dirsign(-1); + } + } + } + } + } + + // Set particle type to electron if needed + int particle_type = 0; + if (sg->has_particle_info() && sg->particle_info()) { + particle_type = sg->particle_info()->pdg(); + } + + if (particle_type == 0 || + (std::abs(particle_type) == 13 && segment_track_length(sg) < 40 * units::cm && sg->dir_weak())) { + auto four_momentum = segment_cal_4mom(sg, 11, particle_data, recomb_model); + + // Create ParticleInfo for electron + auto pinfo = std::make_shared( + 11, // electron PDG + particle_data->get_particle_mass(11), // electron mass + particle_data->pdg_to_name(11), // "electron" + four_momentum // 4-momentum + ); + + sg->particle_info(pinfo); + } + + // Complete shower structure + std::set used_segments; + shower->complete_structure_with_start_segment(used_segments); + showers.insert(shower); + } + } + + update_shower_maps(showers, map_vertex_in_shower, map_segment_in_shower, + map_vertex_to_shower, used_shower_clusters); +} + + +void PatternAlgorithms::examine_shower_1(Graph& graph, VertexPtr main_vertex, std::set& showers, Facade::Cluster* main_cluster, std::vector& other_clusters, std::map map_cluster_main_vertices, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model){ + + if (!main_vertex) return; + + // Build map_vertex_segments + std::map> map_vertex_segments; + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr seg = graph[*eit].segment; + if (!seg) continue; + + auto source_vdesc = boost::source(*eit, graph); + auto target_vdesc = boost::target(*eit, graph); + VertexPtr v1 = graph[source_vdesc].vertex; + VertexPtr v2 = graph[target_vdesc].vertex; + + if (v1) map_vertex_segments[v1].insert(seg); + if (v2) map_vertex_segments[v2].insert(seg); + } + + // Check if there is already a large EM shower connecting to main_vertex + bool flag_skip = false; + auto it = map_vertex_to_shower.find(main_vertex); + if (it != map_vertex_to_shower.end()) { + for (auto shower : it->second) { + if (shower->start_segment() && shower->start_segment()->has_particle_info() && + shower->start_segment()->particle_info()->pdg() != 11) continue; + + auto [start_vtx, conn_type] = shower->get_start_vertex_and_type(); + if (conn_type != 1) continue; + + double energy = 0; + if (shower->get_kine_best() != 0) { + energy = shower->get_kine_best(); + } else { + energy = shower->get_kine_charge(); + } + if (energy > 80 * units::MeV) flag_skip = true; + } + } + + bool flag_added = false; + + if (!flag_skip) { + std::set used_showers; + std::map> map_segment_showers; + std::map map_segment_new_shower; + std::set used_segments; + std::set del_showers; + + // Loop over segments at main_vertex + if (map_vertex_segments.find(main_vertex) != map_vertex_segments.end()) { + WireCell::Point main_vtx_pt = main_vertex->fit().valid() ? main_vertex->fit().point : main_vertex->wcpt().point; + + for (auto sg : map_vertex_segments[main_vertex]) { + // Skip strong direction segments or certain particle types + if (!sg->dir_weak()) continue; + + int particle_type = 0; + if (sg->has_particle_info() && sg->particle_info()) { + particle_type = sg->particle_info()->pdg(); + } + + double medium_dQ_dx = segment_median_dQ_dx(sg); + if (particle_type == 2212 && medium_dQ_dx / (43e3 / units::cm) > 1.6) continue; + if (particle_type == 11) continue; + + // Form a new shower + ShowerPtr shower1 = std::make_shared(graph); + shower1->set_start_vertex(main_vertex, 1); + shower1->set_start_segment(sg); + shower1->complete_structure_with_start_segment(used_segments); + + WireCell::Vector dir1 = segment_cal_dir_3vector(sg, main_vtx_pt, 15 * units::cm); + + // Check against existing showers + for (auto shower : showers) { + double energy = shower->get_kine_charge(); + double min_dis = shower_get_closest_dis(*shower, sg); + + auto [start_vtx, conn_type] = shower->get_start_vertex_and_type(); + + if (conn_type > 2 && min_dis > 3 * units::cm) continue; + if (shower->start_segment() && shower->start_segment()->has_particle_info() && + shower->start_segment()->particle_info()->pdg() != 11) continue; + if (start_vtx == main_vertex && conn_type == 1) continue; + + // Get shower1's vertices + TrajectoryView& traj1 = shower1->fill_maps(); + std::set shower1_vertices; + for (auto vdesc : traj1.nodes()) { + auto vtx = traj1.view_graph()[vdesc].vertex; + if (vtx) shower1_vertices.insert(vtx); + } + + if (conn_type == 1 && energy > 80 * units::MeV) { + if (shower1_vertices.find(start_vtx) == shower1_vertices.end()) continue; + else { + WireCell::Vector dir3 = shower_cal_dir_3vector(*shower, shower->get_start_point(), 15 * units::cm); + double angle = std::acos(std::clamp(dir3.dot(dir1) / (dir3.magnitude() * dir1.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + if (angle > 30) continue; + } + } else { + WireCell::Vector dir3 = shower_cal_dir_3vector(*shower, shower->get_start_point(), 15 * units::cm); + + // Find closest vertex in shower1 to shower start point + WireCell::Point min_point; + double min_vtx_dis = 1e9; + for (auto vtx3 : shower1_vertices) { + WireCell::Point vtx3_pt = vtx3->fit().valid() ? vtx3->fit().point : vtx3->wcpt().point; + double dis = (vtx3_pt - shower->get_start_point()).magnitude(); + if (dis < min_vtx_dis) { + min_vtx_dis = dis; + min_point = vtx3_pt; + } + } + + WireCell::Vector dir4(shower->get_start_point().x() - min_point.x(), + shower->get_start_point().y() - min_point.y(), + shower->get_start_point().z() - min_point.z()); + + double angle3 = std::acos(std::clamp(dir3.dot(dir1) / (dir3.magnitude() * dir1.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + double angle4 = std::acos(std::clamp(dir4.dot(dir1) / (dir4.magnitude() * dir1.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + double tmp_angle = std::min(angle3, angle4); + + if (energy > 25 * units::MeV && tmp_angle > 40) continue; + } + + if (used_showers.find(shower) != used_showers.end()) continue; + + auto [shower_dis, shower_point] = shower_get_closest_point(*shower, main_vtx_pt); + WireCell::Vector dir2(shower_point.x() - main_vtx_pt.x(), + shower_point.y() - main_vtx_pt.y(), + shower_point.z() - main_vtx_pt.z()); + + double angle = std::acos(std::clamp(dir1.dot(dir2) / (dir1.magnitude() * dir2.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + + if ((angle < 15 && min_dis < 36 * units::cm) || + (angle < 10 && min_dis < 46 * units::cm) || + (angle < 7.5)) { + map_segment_showers[sg].insert(shower); + used_showers.insert(shower); + } + } + + if (map_segment_showers.find(sg) != map_segment_showers.end()) { + map_segment_new_shower[sg] = shower1; + } else { + // Shower1 will be deleted by shared_ptr + } + } + } + + // Process segments with associated showers + for (auto& [sg, associated_showers] : map_segment_showers) { + ShowerPtr shower1 = map_segment_new_shower[sg]; + int num_showers = associated_showers.size(); + + double max_energy = 0; + double total_energy = 0; + for (auto shower : associated_showers) { + double energy = shower->get_kine_charge(); + if (energy > max_energy) max_energy = energy; + total_energy += energy; + } + + // Analyze shower1 structure + TrajectoryView& traj = shower1->fill_maps(); + std::set shower1_vertices; + for (auto vdesc : traj.nodes()) { + auto vtx = traj.view_graph()[vdesc].vertex; + if (vtx) shower1_vertices.insert(vtx); + } + + double max_length = 0; + SegmentPtr max_sg = nullptr; + int n_tracks = 0; + int n_showers = 0; + double total_length = 0; + bool flag_good_track = false; + + for (auto edesc : traj.edges()) { + auto sg1 = traj.view_graph()[edesc].segment; + if (!sg1) continue; + + double length = segment_track_length(sg1); + double medium_dQ_dx = segment_median_dQ_dx(sg1) / (43e3 / units::cm); + + if (!sg1->dir_weak()) { + // Find end vertex + auto seg_edesc = sg1->get_descriptor(); + auto source_vdesc = boost::source(seg_edesc, graph); + auto target_vdesc = boost::target(seg_edesc, graph); + VertexPtr v1 = graph[source_vdesc].vertex; + VertexPtr v2 = graph[target_vdesc].vertex; + + VertexPtr end_vertex = nullptr; + if (sg1->dirsign() == 1) { + if (!sg1->fits().empty() && v1 && v2) { + auto& fits = sg1->fits(); + double dist1 = (fits.back().point - (v1->fit().valid() ? v1->fit().point : v1->wcpt().point)).magnitude(); + double dist2 = (fits.back().point - (v2->fit().valid() ? v2->fit().point : v2->wcpt().point)).magnitude(); + end_vertex = (dist1 < dist2) ? v1 : v2; + } + } else if (sg1->dirsign() == -1) { + if (!sg1->fits().empty() && v1 && v2) { + auto& fits = sg1->fits(); + double dist1 = (fits.front().point - (v1->fit().valid() ? v1->fit().point : v1->wcpt().point)).magnitude(); + double dist2 = (fits.front().point - (v2->fit().valid() ? v2->fit().point : v2->wcpt().point)).magnitude(); + end_vertex = (dist1 < dist2) ? v1 : v2; + } + } + + if (end_vertex && map_vertex_segments.find(end_vertex) != map_vertex_segments.end()) { + if (map_vertex_segments[end_vertex].size() > 1) { + bool flag_non_ele = false; + for (auto sg2 : map_vertex_segments[end_vertex]) { + if (sg2 == sg1) continue; + bool is_shower = sg2->flags_any(SegmentFlags::kShowerTrajectory) || + sg2->flags_any(SegmentFlags::kShowerTopology); + if (!is_shower) flag_non_ele = true; + } + if (!flag_non_ele && map_vertex_segments[end_vertex].size() <= 3) { + flag_good_track = true; + } + } else { + flag_good_track = true; + } + } + } + + bool is_shower = sg1->flags_any(SegmentFlags::kShowerTrajectory) || + sg1->flags_any(SegmentFlags::kShowerTopology); + if (is_shower) n_showers++; + + n_tracks++; + total_length += length; + if (max_length < length) { + max_length = length; + max_sg = sg1; + } + (void)medium_dQ_dx; // To avoid unused variable warning + } + (void)flag_good_track; + (void)n_showers; + + // Check if should skip + bool flag_skip_segment = false; + for (auto shower_check : showers) { + auto [start_vtx_check, conn_type_check] = shower_check->get_start_vertex_and_type(); + if (conn_type_check == 1 && associated_showers.find(shower_check) == associated_showers.end()) { + if (shower1_vertices.find(start_vtx_check) != shower1_vertices.end() && + start_vtx_check != main_vertex && + shower_check->get_kine_charge() > 60 * units::MeV) { + flag_skip_segment = true; + } + } + } + + // Decide whether to create this shower + if (total_length < 70 * units::cm && + ((n_tracks == 1 && total_length < 60 * units::cm) || + (n_tracks == 1 && total_length < 65 * units::cm && num_showers > 3 && total_energy > 150 * units::MeV) || + total_length < n_tracks * 36 * units::cm) && + (total_energy > 50 * units::MeV || total_energy / units::MeV > total_length / units::cm * 0.75) && + !flag_skip_segment) { + + // Set particle type to electron + if (shower1->start_segment() && shower1->start_segment()->has_particle_info() && + shower1->start_segment()->particle_info()) { + shower1->start_segment()->particle_info()->set_pdg(11); + } + shower1->start_segment()->set_flags(SegmentFlags::kAvoidMuonCheck); + shower1->update_particle_type(particle_data, recomb_model); + + // Merge associated showers + for (auto shower : associated_showers) { + del_showers.insert(shower); + shower1->add_shower(*shower); + } + + shower1->calculate_kinematics(particle_data, recomb_model); + double kine_charge = cal_kine_charge(shower1, graph, track_fitter, dv); + shower1->set_kine_charge(kine_charge); + shower1->set_flag_kinematics(true); + + showers.insert(shower1); + std::cout << "Create a new low-energy shower: " << kine_charge / units::MeV << " MeV" << std::endl; + flag_added = true; + } + } + + // Remove deleted showers + for (auto shower : del_showers) { + showers.erase(shower); + } + + update_shower_maps(showers, map_vertex_in_shower, map_segment_in_shower, + map_vertex_to_shower, used_shower_clusters); + } + + // Second part: merge existing showers if nothing was added + if (!flag_added) { + std::map> map_shower_showers; + WireCell::Point main_vtx_pt = main_vertex->fit().valid() ? main_vertex->fit().point : main_vertex->wcpt().point; + + auto it = map_vertex_to_shower.find(main_vertex); + if (it != map_vertex_to_shower.end()) { + for (auto shower : it->second) { + if (shower->start_segment() && shower->start_segment()->has_particle_info() && + shower->start_segment()->particle_info()->pdg() != 11) continue; + + auto [start_vtx, conn_type] = shower->get_start_vertex_and_type(); + if (conn_type != 1) continue; + + WireCell::Vector dir1 = shower_cal_dir_3vector(*shower, main_vtx_pt, 15 * units::cm); + + for (auto shower1 : showers) { + double energy = shower1->get_kine_charge(); + double min_dis = shower_get_closest_dis(*shower1, shower->start_segment()); + + auto [start_vtx1, conn_type1] = shower1->get_start_vertex_and_type(); + + if (conn_type1 > 2 && min_dis > 3 * units::cm) continue; + if (shower1->start_segment() && shower1->start_segment()->has_particle_info() && + shower1->start_segment()->particle_info()->pdg() != 11) continue; + if (start_vtx1 == main_vertex && conn_type1 == 1) continue; + + // Get shower's vertices + TrajectoryView& traj_shower = shower->fill_maps(); + std::set shower_vertices; + for (auto vdesc : traj_shower.nodes()) { + auto vtx = traj_shower.view_graph()[vdesc].vertex; + if (vtx) shower_vertices.insert(vtx); + } + + if (conn_type1 == 1 && shower_vertices.find(start_vtx1) == shower_vertices.end()) continue; + + if (shower1->get_total_length() < 3 * units::cm) continue; + + auto [shower_dis, shower_point] = shower_get_closest_point(*shower1, main_vtx_pt); + WireCell::Vector dir2(shower_point.x() - main_vtx_pt.x(), + shower_point.y() - main_vtx_pt.y(), + shower_point.z() - main_vtx_pt.z()); + + double angle = std::acos(std::clamp(dir1.dot(dir2) / (dir1.magnitude() * dir2.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + + WireCell::Vector dir3 = shower_cal_dir_3vector(*shower1, shower1->get_start_point(), 30 * units::cm); + double angle1 = std::acos(std::clamp(dir2.dot(dir3) / (dir2.magnitude() * dir3.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + + if (angle < 15 && angle1 < 15 && min_dis < 28 * units::cm) { + map_shower_showers[shower].insert(shower1); + } + (void)energy; // To avoid unused variable warning + } + } + } + + // Find the shower combination with maximum energy + std::set del_showers; + ShowerPtr max_shower = nullptr; + double max_energy = 0; + + for (auto& [shower, associated_showers] : map_shower_showers) { + double acc_energy = 0; + if (shower->get_kine_best() != 0) { + acc_energy += shower->get_kine_best(); + } else { + acc_energy += shower->get_kine_charge(); + } + + for (auto shower1 : associated_showers) { + if (shower1->get_kine_best() != 0) { + acc_energy += shower1->get_kine_best(); + } else { + acc_energy += shower1->get_kine_charge(); + } + } + + if (acc_energy > max_energy) { + max_energy = acc_energy; + max_shower = shower; + } + } + + if (max_shower) { + for (auto shower1 : map_shower_showers[max_shower]) { + max_shower->add_shower(*shower1); + del_showers.insert(shower1); + } + + max_shower->calculate_kinematics(particle_data, recomb_model); + max_shower->start_segment()->set_flags(SegmentFlags::kAvoidMuonCheck); + double kine_charge = cal_kine_charge(max_shower, graph, track_fitter, dv); + max_shower->set_kine_charge(kine_charge); + max_shower->set_flag_kinematics(true); + } + + // Remove deleted showers + for (auto shower : del_showers) { + showers.erase(shower); + } + + update_shower_maps(showers, map_vertex_in_shower, map_segment_in_shower, + map_vertex_to_shower, used_shower_clusters); + } +} + + +void PatternAlgorithms::examine_showers(Graph& graph, VertexPtr main_vertex, std::set& showers, Facade::Cluster* main_cluster, std::vector& other_clusters, std::map map_cluster_main_vertices, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model){ + + if (!main_vertex) return; + + // Build map_vertex_segments + std::map> map_vertex_segments; + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr seg = graph[*eit].segment; + if (!seg) continue; + + auto source_vdesc = boost::source(*eit, graph); + auto target_vdesc = boost::target(*eit, graph); + VertexPtr v1 = graph[source_vdesc].vertex; + VertexPtr v2 = graph[target_vdesc].vertex; + + if (v1) map_vertex_segments[v1].insert(seg); + if (v2) map_vertex_segments[v2].insert(seg); + } + + std::map map_merge_seg_shower; + WireCell::Vector drift_dir(1, 0, 0); + std::set del_showers; + + WireCell::Point main_vtx_pt = main_vertex->fit().valid() ? main_vertex->fit().point : main_vertex->wcpt().point; + + // Loop over segments at main_vertex + if (map_vertex_segments.find(main_vertex) != map_vertex_segments.end()) { + for (auto sg : map_vertex_segments[main_vertex]) { + // Skip if already in shower and not a muon + if (map_segment_in_shower.find(sg) != map_segment_in_shower.end()) { + if (sg->has_particle_info() && sg->particle_info()->pdg() != 13) continue; + } + + double sg_length = segment_track_length(sg); + + // Skip long segments + if ((sg_length > 45 * units::cm && !sg->dir_weak()) || sg_length > 55 * units::cm) continue; + + // Find other vertex + auto seg_edesc = sg->get_descriptor(); + auto source_vdesc = boost::source(seg_edesc, graph); + auto target_vdesc = boost::target(seg_edesc, graph); + VertexPtr v1 = graph[source_vdesc].vertex; + VertexPtr v2 = graph[target_vdesc].vertex; + VertexPtr vtx = (v1 == main_vertex) ? v2 : v1; + if (!vtx) continue; + + auto daughter_result = calculate_num_daughter_showers(graph, main_vertex, sg, false); + double daughter_length = daughter_result.second; + + bool flag_checked = false; + + // Case I: Check showers at the other vertex + if (map_vertex_to_shower.find(vtx) != map_vertex_to_shower.end()) { + WireCell::Point vtx_pt = vtx->fit().valid() ? vtx->fit().point : vtx->wcpt().point; + WireCell::Vector dir1 = segment_cal_dir_3vector(sg, vtx_pt, 15 * units::cm); + WireCell::Vector dir1_1 = segment_cal_dir_3vector(sg, main_vtx_pt, 15 * units::cm); + bool flag_tmp_connected = false; + + // First pass: check for high-energy directly connected showers + for (auto shower : map_vertex_to_shower[vtx]) { + if (shower->start_segment() && shower->start_segment()->has_particle_info() && + shower->start_segment()->particle_info()->pdg() != 11) continue; + + auto [start_vtx, conn_type] = shower->get_start_vertex_and_type(); + double Eshower = (shower->get_kine_best() != 0) ? shower->get_kine_best() : shower->get_kine_charge(); + + if (conn_type == 1 && Eshower > 60 * units::MeV) flag_tmp_connected = true; + } + + // Second pass: check merging conditions + for (auto shower : map_vertex_to_shower[vtx]) { + if (shower->start_segment() && shower->start_segment()->has_particle_info() && + shower->start_segment()->particle_info()->pdg() != 11) continue; + + auto [start_vtx, conn_type] = shower->get_start_vertex_and_type(); + WireCell::Vector dir2 = shower_cal_dir_3vector(*shower, shower->get_start_point(), 100 * units::cm); + + double Eshower = (shower->get_kine_best() != 0) ? shower->get_kine_best() : shower->get_kine_charge(); + + // Check shower composition + TrajectoryView& traj = shower->fill_maps(); + double tmp_total_length = 0; + double tmp_track_length = 0; + + for (auto edesc : traj.edges()) { + auto sg1 = traj.view_graph()[edesc].segment; + if (!sg1) continue; + if (sg1->cluster() != shower->start_segment()->cluster()) continue; + + double length = segment_track_length(sg1); + tmp_total_length += length; + if (!sg1->dir_weak()) tmp_track_length += length; + } + + if (tmp_track_length > 3 * units::cm && tmp_track_length > 0.25 * tmp_total_length) continue; + + if (conn_type == 1 && Eshower > 100 * units::MeV) flag_checked = true; + + double angle1 = std::acos(std::clamp(dir1.dot(dir2) / (dir1.magnitude() * dir2.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + double angle2 = std::acos(std::clamp(dir1_1.dot(dir2) / (dir1_1.magnitude() * dir2.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + double tmp_angle = std::min(180.0 - angle1, angle2); + + auto [closest_dis, closest_pt] = segment_get_closest_point(sg, shower->get_start_point()); + + // Special checks for connection type 2 + if (conn_type == 2) { + if ((!sg->dir_weak() && sg_length > 3 * units::cm) || flag_tmp_connected) { + if (closest_dis < 8 * units::cm && Eshower > 75 * units::MeV && tmp_angle < 6) { + // Allow this condition + } else { + continue; + } + } + if (closest_dis > 20 * units::cm && Eshower < 150 * units::MeV && tmp_angle > 2.5) continue; + } + + // Main merging conditions + if ((Eshower > 800 * units::MeV && tmp_angle < 30) || + (Eshower > 150 * units::MeV && tmp_angle < 10) || + (Eshower > 150 * units::MeV && tmp_angle < 18 && conn_type == 1 && sg->dir_weak()) || + (Eshower > 100 * units::MeV && tmp_angle < 10 && sg_length < 25 * units::cm) || + (Eshower > 250 * units::MeV && tmp_angle < 15) || + (Eshower > 360 * units::MeV && tmp_angle < 25) || + (Eshower > 100 * units::MeV && Eshower <= 150 * units::MeV && tmp_angle < 15 && + sg_length < 25 * units::cm && flag_checked) || + (Eshower > 60 * units::MeV && conn_type == 2 && sg->dir_weak() && + ((tmp_angle < 15 && closest_dis < 18 * units::cm) || + (tmp_angle < 17.5 && closest_dis < 6 * units::cm)) && + sg_length < 15 * units::cm) || + (Eshower > 60 * units::MeV && conn_type == 2 && tmp_angle < 7.5 && + closest_dis < 8 * units::cm && sg_length < 20 * units::cm)) { + map_merge_seg_shower[sg] = shower; + continue; + } + } + } + + if (map_merge_seg_shower.find(sg) != map_merge_seg_shower.end()) continue; + if (flag_checked) continue; + if (!sg->dir_weak() && sg_length > 6 * units::cm && daughter_length < 40 * units::cm) continue; + + // Case II: Check showers at main_vertex (not directly connected) + if (map_vertex_to_shower.find(main_vertex) != map_vertex_to_shower.end()) { + WireCell::Vector dir1 = segment_cal_dir_3vector(sg, main_vtx_pt, 15 * units::cm); + + for (auto shower : map_vertex_to_shower[main_vertex]) { + if (shower->start_segment() && shower->start_segment()->has_particle_info() && + shower->start_segment()->particle_info()->pdg() != 11) continue; + + auto [start_vtx, conn_type] = shower->get_start_vertex_and_type(); + if (conn_type == 1) continue; // Skip directly connected + + WireCell::Vector dir2(shower->get_start_point().x() - main_vtx_pt.x(), + shower->get_start_point().y() - main_vtx_pt.y(), + shower->get_start_point().z() - main_vtx_pt.z()); + WireCell::Vector dir3 = shower_cal_dir_3vector(*shower, shower->get_start_point(), 100 * units::cm); + + auto [min_dis, closest_pt] = segment_get_closest_point(sg, shower->get_start_point()); + + double angle_dir2 = std::acos(std::clamp(dir1.dot(dir2) / (dir1.magnitude() * dir2.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + double angle_dir3 = std::acos(std::clamp(dir1.dot(dir3) / (dir1.magnitude() * dir3.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + double angle_drift1 = std::acos(std::clamp(dir1.dot(drift_dir) / (dir1.magnitude() * drift_dir.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + double angle_drift3 = std::acos(std::clamp(dir3.dot(drift_dir) / (dir3.magnitude() * drift_dir.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + + if (((shower->get_kine_charge() > 80 * units::MeV && angle_dir2 < 10) || + (shower->get_kine_charge() > 50 * units::MeV && angle_dir2 < 3) || + (shower->get_kine_charge() > 80 * units::MeV && angle_dir3 < 6 && angle_dir2 < 17.5) || + (shower->get_kine_charge() > 80 * units::MeV && angle_dir3 < 6 && + std::fabs(90 - angle_drift1) < 10 && std::fabs(90 - angle_drift3) < 10 && angle_dir2 < 30)) && + (sg_length > 5 * units::cm || (sg_length > 3 * units::cm && min_dis < 2.0 * units::cm))) { + map_merge_seg_shower[sg] = shower; + continue; + } + } + } + + // Case III: Check other showers + WireCell::Vector dir1 = segment_cal_dir_3vector(sg, main_vtx_pt, 15 * units::cm); + + for (auto shower : showers) { + auto [start_vtx_shower, conn_type_shower] = shower->get_start_vertex_and_type(); + if (start_vtx_shower == vtx || start_vtx_shower == main_vertex) continue; + + if (shower->start_segment() && shower->start_segment()->has_particle_info() && + shower->start_segment()->particle_info()->pdg() != 11) continue; + + if (conn_type_shower <= 2) { + WireCell::Vector dir2(shower->get_start_point().x() - main_vtx_pt.x(), + shower->get_start_point().y() - main_vtx_pt.y(), + shower->get_start_point().z() - main_vtx_pt.z()); + WireCell::Vector dir3 = shower_cal_dir_3vector(*shower, shower->get_start_point(), 100 * units::cm); + + double angle_dir2 = std::acos(std::clamp(dir1.dot(dir2) / (dir1.magnitude() * dir2.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + double angle_dir3 = std::acos(std::clamp(dir2.dot(dir3) / (dir2.magnitude() * dir3.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + + if ((((shower->get_kine_charge() > 80 * units::MeV && angle_dir2 < 15) || + (shower->get_kine_charge() > 50 * units::MeV && angle_dir2 < 5)) && + angle_dir3 < 15 && (angle_dir2 + angle_dir3) < 25 && sg_length > 5 * units::cm)) { + map_merge_seg_shower[sg] = shower; + continue; + } + } + } + } + } + + // Mark showers for deletion if their start segment should be merged + for (auto shower : showers) { + SegmentPtr sg1 = shower->start_segment(); + if (sg1 && map_merge_seg_shower.find(sg1) != map_merge_seg_shower.end()) { + sg1->set_flags(SegmentFlags::kAvoidMuonCheck); + del_showers.insert(shower); + } + } + + // Delete marked showers first + if (del_showers.size() != 0) { + for (auto shower1 : del_showers) { + showers.erase(shower1); + } + del_showers.clear(); + update_shower_maps(showers, map_vertex_in_shower, map_segment_in_shower, + map_vertex_to_shower, used_shower_clusters); + } + + // Perform the merging + std::set updated_showers; + for (auto& [sg, shower] : map_merge_seg_shower) { + std::cout << "EM shower modification: " << shower->start_segment()->id() << " -> " << sg->id() << std::endl; + updated_showers.insert(shower); + + auto [pair_vertex, pair_conn_type] = shower->get_start_vertex_and_type(); + + if (pair_conn_type != 1) { + shower->add_segment(sg); + shower->set_start_vertex(main_vertex, 1); + shower->set_start_segment(sg); + shower->set_start_point(main_vtx_pt); + std::set tmp_used_segments; + shower->complete_structure_with_start_segment(tmp_used_segments); + if (segment_track_length(sg) > 44 * units::cm || sg->dir_weak()) { + sg->set_flags(SegmentFlags::kAvoidMuonCheck); + } + } else { + shower->add_segment(sg); + shower->set_start_vertex(main_vertex, 1); + shower->set_start_segment(sg); + shower->set_start_point(main_vtx_pt); + std::set tmp_used_segments; + shower->complete_structure_with_start_segment(tmp_used_segments); + if (shower->get_num_main_segments() >= 3) { + sg->set_flags(SegmentFlags::kAvoidMuonCheck); + } + } + + // Add other showers that connect to this shower + TrajectoryView& traj = shower->fill_maps(); + std::set shower_vertices; + for (auto vdesc : traj.nodes()) { + auto vtx = traj.view_graph()[vdesc].vertex; + if (vtx) shower_vertices.insert(vtx); + } + + for (auto shower1 : showers) { + if (shower == shower1) continue; + auto [start_vtx1, conn_type1] = shower1->get_start_vertex_and_type(); + if (conn_type1 == 1 && start_vtx1 != main_vertex) { + if (shower_vertices.find(start_vtx1) != shower_vertices.end()) { + shower->add_shower(*shower1); + del_showers.insert(shower1); + } + } + } + + // Update particle type and kinematics + if (sg->has_particle_info() && sg->particle_info()) { + sg->particle_info()->set_pdg(11); + } + shower->update_particle_type(particle_data, recomb_model); + shower->calculate_kinematics(particle_data, recomb_model); + double kine_charge = cal_kine_charge(shower, graph, track_fitter, dv); + shower->set_kine_charge(kine_charge); + shower->set_flag_kinematics(true); + } + + // Delete merged showers (not at main_vertex) + for (auto shower1 : del_showers) { + auto [start_vtx1, conn_type1] = shower1->get_start_vertex_and_type(); + if (start_vtx1 != main_vertex) { + showers.erase(shower1); + } + } + del_showers.clear(); + + // Check other showers and merge with updated showers + for (auto shower : updated_showers) { + WireCell::Point shower_start = shower->get_start_point(); + WireCell::Vector dir1 = shower_cal_dir_3vector(*shower, shower_start, 25 * units::cm); + + for (auto shower1 : showers) { + if (updated_showers.find(shower1) != updated_showers.end()) continue; + if (shower1->start_segment() && shower1->start_segment()->has_particle_info() && + shower1->start_segment()->particle_info()->pdg() != 11) continue; + + auto [start_vtx1, conn_type1] = shower1->get_start_vertex_and_type(); + if (conn_type1 == 2) { + if (del_showers.find(shower1) != del_showers.end()) continue; + + auto [shower_vtx, shower_conn] = shower->get_start_vertex_and_type(); + WireCell::Point shower_vtx_pt = shower_vtx->fit().valid() ? shower_vtx->fit().point : shower_vtx->wcpt().point; + + WireCell::Vector dir2(shower1->get_start_point().x() - shower_vtx_pt.x(), + shower1->get_start_point().y() - shower_vtx_pt.y(), + shower1->get_start_point().z() - shower_vtx_pt.z()); + WireCell::Vector dir3 = shower_cal_dir_3vector(*shower1, shower1->get_start_point(), 25 * units::cm); + + double angle_dir2 = std::acos(std::clamp(dir1.dot(dir2) / (dir1.magnitude() * dir2.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + double angle_dir3 = std::acos(std::clamp(dir1.dot(dir3) / (dir1.magnitude() * dir3.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + + if (angle_dir2 < 10 && angle_dir3 < 20) { + shower->add_shower(*shower1); + shower->calculate_kinematics(particle_data, recomb_model); + double kine_charge = cal_kine_charge(shower, graph, track_fitter, dv); + shower->set_kine_charge(kine_charge); + shower->set_flag_kinematics(true); + del_showers.insert(shower1); + } + } + } + } + + // Final deletion + for (auto shower1 : del_showers) { + showers.erase(shower1); + } + + if (map_merge_seg_shower.size() > 0) { + update_shower_maps(showers, map_vertex_in_shower, map_segment_in_shower, + map_vertex_to_shower, used_shower_clusters); + } + + // Finally call examine_shower_1 + examine_shower_1(graph, main_vertex, showers, main_cluster, other_clusters, map_cluster_main_vertices, + map_vertex_in_shower, map_segment_in_shower, map_vertex_to_shower, used_shower_clusters, + track_fitter, dv, particle_data, recomb_model); +} + + +void PatternAlgorithms::id_pi0_with_vertex(int acc_segment_id, std::set& pi0_showers, std::map& map_shower_pio_id, std::map >& map_pio_id_showers, std::map >& map_pio_id_mass, std::map >& map_pio_id_saved_pair, Graph& graph, VertexPtr main_vertex, std::set& showers, Facade::Cluster* main_cluster, std::vector& other_clusters, std::map map_cluster_main_vertices, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model){ + + if (!main_vertex) return; + + // Build map_vertex_segments + std::map> map_vertex_segments; + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr seg = graph[*eit].segment; + if (!seg) continue; + + auto source_vdesc = boost::source(*eit, graph); + auto target_vdesc = boost::target(*eit, graph); + VertexPtr v1 = graph[source_vdesc].vertex; + VertexPtr v2 = graph[target_vdesc].vertex; + + if (v1) map_vertex_segments[v1].insert(seg); + if (v2) map_vertex_segments[v2].insert(seg); + } + + // Figure out all disconnected showers + std::set disconnected_showers; + std::map map_shower_dir; + + for (auto& [vtx, shower_set] : map_vertex_to_shower) { + for (auto shower : shower_set) { + auto [start_vtx, conn_type] = shower->get_start_vertex_and_type(); + + if (conn_type == 2 && std::abs(shower->get_particle_type()) != 13) { + disconnected_showers.insert(shower); + map_shower_dir[shower] = shower_cal_dir_3vector(*shower, shower->get_start_point(), 15 * units::cm); + } else if (conn_type == 1) { + WireCell::Point start_vtx_pt = start_vtx->fit().valid() ? start_vtx->fit().point : start_vtx->wcpt().point; + map_shower_dir[shower] = shower_cal_dir_3vector(*shower, start_vtx_pt, 15 * units::cm); + } + } + } + + // Build candidate vertices + std::set candidate_vertices; + candidate_vertices.insert(main_vertex); + + for (auto& [vtx, shower_set] : map_vertex_to_shower) { + bool flag_add = true; + auto it_in_shower = map_vertex_in_shower.find(vtx); + if (it_in_shower != map_vertex_in_shower.end()) { + flag_add = false; + auto [start_vtx, conn_type] = it_in_shower->second->get_start_vertex_and_type(); + if (vtx == start_vtx) flag_add = true; + } + if (flag_add) candidate_vertices.insert(vtx); + } + + // Map shower pairs to masses and vertices + std::map, std::vector>> map_shower_pair_mass_vertex; + + for (auto vtx : candidate_vertices) { + std::vector tmp_showers; + std::map local_dirs; + + WireCell::Point vtx_pt = vtx->fit().valid() ? vtx->fit().point : vtx->wcpt().point; + + // Add directly connected showers (type 1, not muon) + auto it2 = map_vertex_to_shower.find(vtx); + if (it2 != map_vertex_to_shower.end()) { + for (auto shower : it2->second) { + auto [start_vtx, conn_type] = shower->get_start_vertex_and_type(); + if (conn_type == 1 && std::abs(shower->get_particle_type()) != 13) { + tmp_showers.push_back(shower); + local_dirs[shower] = shower->get_init_dir(); + } + } + } + + // Add disconnected showers within angle + for (auto shower : disconnected_showers) { + WireCell::Vector dir1 = map_shower_dir[shower]; + WireCell::Vector dir2(shower->get_start_point().x() - vtx_pt.x(), + shower->get_start_point().y() - vtx_pt.y(), + shower->get_start_point().z() - vtx_pt.z()); + + auto [start_vtx, conn_type] = shower->get_start_vertex_and_type(); + + if (start_vtx == vtx) { + tmp_showers.push_back(shower); + local_dirs[shower] = shower->get_init_dir(); + } else { + double angle = std::acos(std::clamp(dir1.dot(dir2) / (dir1.magnitude() * dir2.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + if (angle < 30) { + tmp_showers.push_back(shower); + local_dirs[shower] = dir2; + } + } + } + + // Calculate pi0 masses for all pairs + if (tmp_showers.size() > 1) { + for (size_t i = 0; i < tmp_showers.size(); i++) { + ShowerPtr shower_1 = tmp_showers[i]; + WireCell::Vector dir1 = local_dirs[shower_1]; + + for (size_t j = i + 1; j < tmp_showers.size(); j++) { + ShowerPtr shower_2 = tmp_showers[j]; + WireCell::Vector dir2 = local_dirs[shower_2]; + + double angle = std::acos(std::clamp(dir1.dot(dir2) / (dir1.magnitude() * dir2.magnitude()), -1.0, 1.0)); + double mass_pio = std::sqrt(4 * shower_1->get_kine_charge() * shower_2->get_kine_charge() * + std::pow(std::sin(angle / 2.0), 2)); + + auto [start_vtx_1, conn_type_1] = shower_1->get_start_vertex_and_type(); + auto [start_vtx_2, conn_type_2] = shower_2->get_start_vertex_and_type(); + + if (conn_type_1 == 1 && conn_type_2 == 1) continue; + + map_shower_pair_mass_vertex[std::make_pair(shower_1, shower_2)].push_back(std::make_pair(mass_pio, vtx)); + } + } + } + } + + // Store pi0 kinematic variables (not implemented as member variables yet, just processing) + WireCell::Point main_vtx_pt = main_vertex->fit().valid() ? main_vertex->fit().point : main_vertex->wcpt().point; + + // Find pi0 iteratively + // static int acc_segment_id = 100000; // Static counter for pi0 IDs + + while (map_shower_pair_mass_vertex.size() > 0) { + double mass_diff = 1e9; + double mass_save = 0; + ShowerPtr shower_1 = nullptr; + ShowerPtr shower_2 = nullptr; + double mass_offset = 10 * units::MeV; + VertexPtr vtx = nullptr; + double mass_penalty = 0; + double tmp_mass_penalty = 0; + + // Find best pi0 candidate + for (auto& [shower_pair, mass_vtx_vec] : map_shower_pair_mass_vertex) { + for (auto& [mass, candidate_vtx] : mass_vtx_vec) { + auto [start_vtx_1, conn_type_1] = shower_pair.first->get_start_vertex_and_type(); + auto [start_vtx_2, conn_type_2] = shower_pair.second->get_start_vertex_and_type(); + + if (conn_type_1 == 2 && conn_type_2 == 2) { + tmp_mass_penalty = 6 * units::MeV; + } else { + tmp_mass_penalty = 0; + } + + if (mass - 135 * units::MeV + mass_offset < 35 * units::MeV && + mass - 135 * units::MeV + mass_offset > -25 * units::MeV) { + if (std::abs(mass - 135 * units::MeV + mass_offset) - tmp_mass_penalty < + std::abs(mass_diff) - mass_penalty) { + mass_diff = mass - 135 * units::MeV + mass_offset; + mass_penalty = tmp_mass_penalty; + mass_save = mass; + shower_1 = shower_pair.first; + shower_2 = shower_pair.second; + vtx = candidate_vtx; + } + } + } + } + + // If found a good pi0, mark it + if (mass_diff < 35 * units::MeV && mass_diff > -25 * units::MeV) { + pi0_showers.insert(shower_1); + pi0_showers.insert(shower_2); + + int pio_id = acc_segment_id; + acc_segment_id++; + + map_shower_pio_id[shower_1] = pio_id; + map_shower_pio_id[shower_2] = pio_id; + map_pio_id_mass[pio_id] = std::make_pair(mass_save, 1); + map_pio_id_showers[pio_id].push_back(shower_1); + map_pio_id_showers[pio_id].push_back(shower_2); + + // Update shower start vertices if needed + auto [start_vtx_1, conn_type_1] = shower_1->get_start_vertex_and_type(); + if (start_vtx_1 != vtx) { + shower_1->set_start_vertex(vtx, 2); + shower_1->calculate_kinematics(particle_data, recomb_model); + } + + auto [start_vtx_2, conn_type_2] = shower_2->get_start_vertex_and_type(); + if (start_vtx_2 != vtx) { + shower_2->set_start_vertex(vtx, 2); + shower_2->calculate_kinematics(particle_data, recomb_model); + } + + std::cout << "Pi0 found with mass: " << mass_save / units::MeV << " MeV with " + << shower_1->get_kine_charge() / units::MeV << " MeV + " + << shower_2->get_kine_charge() / units::MeV << " MeV" << std::endl; + } else { + break; + } + + // Remove pairs involving these showers + std::vector> to_be_removed; + for (auto& [shower_pair, mass_vtx_vec] : map_shower_pair_mass_vertex) { + if (shower_pair.first == shower_1 || shower_pair.first == shower_2 || + shower_pair.second == shower_1 || shower_pair.second == shower_2) { + to_be_removed.push_back(shower_pair); + } + } + for (auto& pair : to_be_removed) { + map_shower_pair_mass_vertex.erase(pair); + } + } + + // Find pi0 vertices and change incoming muons to pions + std::set pi0_vertices; + for (auto shower : pi0_showers) { + auto [start_vtx, conn_type] = shower->get_start_vertex_and_type(); + pi0_vertices.insert(start_vtx); + } + + for (auto vtx : pi0_vertices) { + if (map_vertex_segments.find(vtx) == map_vertex_segments.end()) continue; + + WireCell::Point vtx_pt = vtx->fit().valid() ? vtx->fit().point : vtx->wcpt().point; + + for (auto sg : map_vertex_segments[vtx]) { + // Determine if segment starts or ends at this vertex + bool flag_start = false; + if (!sg->fits().empty()) { + double dist_front = (sg->fits().front().point - vtx_pt).magnitude(); + double dist_back = (sg->fits().back().point - vtx_pt).magnitude(); + flag_start = (dist_front < dist_back); + } + + int dirsign_val = sg->dirsign(); + + // Check if segment is coming in (opposite direction) + bool is_incoming = (flag_start && dirsign_val == -1) || (!flag_start && dirsign_val == 1); + + if (is_incoming && sg->has_particle_info()) { + int pdg = sg->particle_info()->pdg(); + if (std::abs(pdg) == 13 || pdg == 0) { + // Change muon to pion + sg->particle_info()->set_pdg(211); + sg->particle_info()->set_mass(139.57 * units::MeV); // Pion mass + } + } + } + } +} + + +void PatternAlgorithms::id_pi0_without_vertex(int acc_segment_id, std::set& pi0_showers, std::map& map_shower_pio_id, std::map >& map_pio_id_showers, std::map >& map_pio_id_mass, std::map >& map_pio_id_saved_pair, Graph& graph, VertexPtr main_vertex, std::set& showers, Facade::Cluster* main_cluster, std::vector& other_clusters, std::map map_cluster_main_vertices, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model){ + + if (!main_vertex) return; + + // Build map_vertex_segments and segments_in_long_muon + std::map> map_vertex_segments; + std::set segments_in_long_muon; // Placeholder - would need proper implementation + + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr seg = graph[*eit].segment; + if (!seg) continue; + + auto source_vdesc = boost::source(*eit, graph); + auto target_vdesc = boost::target(*eit, graph); + VertexPtr v1 = graph[source_vdesc].vertex; + VertexPtr v2 = graph[target_vdesc].vertex; + + if (v1) map_vertex_segments[v1].insert(seg); + if (v2) map_vertex_segments[v2].insert(seg); + } + + // Check main vertex conditions + if (map_vertex_segments[main_vertex].size() > 2) return; + + if (map_vertex_segments[main_vertex].size() > 0) { + auto first_seg = *map_vertex_segments[main_vertex].begin(); + auto last_seg = *map_vertex_segments[main_vertex].rbegin(); + + if ((map_segment_in_shower.find(first_seg) == map_segment_in_shower.end() && + map_segment_in_shower.find(last_seg) == map_segment_in_shower.end()) || + segments_in_long_muon.find(first_seg) != segments_in_long_muon.end() || + segments_in_long_muon.find(last_seg) != segments_in_long_muon.end()) { + return; + } + } + + // Build good_showers set + std::set good_showers; + { + auto it = map_vertex_to_shower.find(main_vertex); + if (it != map_vertex_to_shower.end()) { + for (auto shower : it->second) { + if (pi0_showers.find(shower) != pi0_showers.end()) return; + + auto [start_vtx, conn_type] = shower->get_start_vertex_and_type(); + if (conn_type == 1) { + good_showers.insert(shower); + } + } + } + + if (good_showers.size() > 1) { + ShowerPtr max_shower = nullptr; + double max_energy = 0; + for (auto shower : good_showers) { + double energy = shower->get_kine_charge(); + if (energy > max_energy) { + max_energy = energy; + max_shower = shower; + } + } + good_showers.clear(); + if (max_shower) good_showers.insert(max_shower); + } + } + + // Check if we have exactly 2 segments at main vertex + if (map_vertex_segments[main_vertex].size() == 2) { + bool flag_return = true; + int num_showers = 0; + + for (auto sg : map_vertex_segments[main_vertex]) { + if (map_segment_in_shower.find(sg) == map_segment_in_shower.end()) { + double sg_length = segment_track_length(sg); + if (sg_length < 1.2 * units::cm && (sg->dirsign() == 0 || sg->dir_weak())) { + flag_return = false; + } + } else { + num_showers++; + } + } + + if (flag_return && num_showers == map_vertex_segments[main_vertex].size()) { + flag_return = false; + } + if (flag_return) return; + } + + // Build map of showers to rays (lines) + std::map map_shower_ray; + WireCell::Point main_vtx_pt = main_vertex->fit().valid() ? main_vertex->fit().point : main_vertex->wcpt().point; + + // Add showers from main vertex + auto it_main = map_vertex_to_shower.find(main_vertex); + if (it_main != map_vertex_to_shower.end()) { + for (auto shower : it_main->second) { + if (shower->get_particle_type() == 13) continue; + if (shower->get_total_length() < 3 * units::cm) continue; + if (pi0_showers.find(shower) != pi0_showers.end()) continue; + + WireCell::Point test_p = shower->get_start_point(); + WireCell::Vector dir = shower_cal_dir_3vector(*shower, test_p, 15 * units::cm); + WireCell::Point p2(test_p.x() + dir.x(), test_p.y() + dir.y(), test_p.z() + dir.z()); + map_shower_ray[shower] = WireCell::Ray(test_p, p2); + } + } + + // Add showers from other vertices + for (auto& [vtx, shower_set] : map_vertex_to_shower) { + if (vtx == main_vertex) continue; + + for (auto shower : shower_set) { + if (shower->get_particle_type() == 13) continue; + if (shower->get_total_length() < 3 * units::cm) continue; + if (pi0_showers.find(shower) != pi0_showers.end()) continue; + + auto [start_vtx, conn_type] = shower->get_start_vertex_and_type(); + if (conn_type != 3) continue; + + if (!shower->start_segment()->flags_any(SegmentFlags::kShowerTrajectory) && + !shower->start_segment()->flags_any(SegmentFlags::kShowerTopology)) continue; + + auto [closest_dis, test_p] = shower_get_closest_point(*shower, main_vtx_pt); + WireCell::Vector dir = shower_cal_dir_3vector(*shower, test_p, 15 * units::cm); + WireCell::Point p2(test_p.x() + dir.x(), test_p.y() + dir.y(), test_p.z() + dir.z()); + map_shower_ray[shower] = WireCell::Ray(test_p, p2); + } + } + + if (map_shower_ray.size() > 1) { + // Calculate pi0 masses for shower pairs + std::map, std::pair> map_shower_pair_mass_point; + + for (auto it = map_shower_ray.begin(); it != map_shower_ray.end(); it++) { + ShowerPtr shower_1 = it->first; + WireCell::Ray ray1 = it->second; + double length_1 = shower_1->get_total_length(); + + for (auto it1 = it; it1 != map_shower_ray.end(); it1++) { + ShowerPtr shower_2 = it1->first; + if (shower_1 == shower_2) continue; + + WireCell::Ray ray2 = it1->second; + if (ray1.first == ray2.first) continue; + + double length_2 = shower_2->get_total_length(); + + WireCell::Vector dir1 = ray_vector(ray1); + WireCell::Vector dir2 = ray_vector(ray2); + double angle_between = std::acos(std::clamp(dir1.dot(dir2) / (dir1.magnitude() * dir2.magnitude()), -1.0, 1.0)); + if (angle_between == 0) continue; + + auto [p1_closest, p2_closest] = ray_closest_points(ray1, ray2); + WireCell::Point center((p1_closest.x() + p2_closest.x()) / 2.0, + (p1_closest.y() + p2_closest.y()) / 2.0, + (p1_closest.z() + p2_closest.z()) / 2.0); + + if (length_1 > 15 * units::cm && length_2 > 15 * units::cm) { + WireCell::Vector dir_to_shower1(ray1.first.x() - center.x(), + ray1.first.y() - center.y(), + ray1.first.z() - center.z()); + WireCell::Vector dir_to_shower2(ray2.first.x() - center.x(), + ray2.first.y() - center.y(), + ray2.first.z() - center.z()); + + if (dir_to_shower1.magnitude() < 3 * units::cm) dir_to_shower1 = dir1; + if (dir_to_shower2.magnitude() < 3 * units::cm) dir_to_shower2 = dir2; + + double angle1 = std::acos(std::clamp(dir_to_shower1.dot(dir1) / (dir_to_shower1.magnitude() * dir1.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + double angle2 = std::acos(std::clamp(dir_to_shower2.dot(dir2) / (dir_to_shower2.magnitude() * dir2.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + + if (angle1 > 25 || angle2 > 25) continue; + + double angle = std::acos(std::clamp(dir_to_shower1.dot(dir_to_shower2) / (dir_to_shower1.magnitude() * dir_to_shower2.magnitude()), -1.0, 1.0)); + double mass_pio = std::sqrt(4 * shower_1->get_kine_charge() * shower_2->get_kine_charge() * + std::pow(std::sin(angle / 2.0), 2)); + map_shower_pair_mass_point[std::make_pair(shower_1, shower_2)] = std::make_pair(mass_pio, center); + + } else if (length_1 > 15 * units::cm || length_2 > 15 * units::cm) { + WireCell::Vector dir_to_c1, dir_to_c2; + + if (length_1 > length_2) { + center = WireCell::Point((p1_closest.x() + p2_closest.x()) / 2.0, + (p1_closest.y() + p2_closest.y()) / 2.0, + (p1_closest.z() + p2_closest.z()) / 2.0); + + auto [dis2, test_p] = shower_get_closest_point(*shower_2, center); + WireCell::Vector dir3 = shower_cal_dir_3vector(*shower_2, test_p, 15 * units::cm); + WireCell::Point p3(test_p.x() + dir3.x(), test_p.y() + dir3.y(), test_p.z() + dir3.z()); + WireCell::Ray ray3(test_p, p3); + + auto [new_p1, new_p2] = ray_closest_points(ray1, ray3); + center = new_p1; + + dir_to_c1 = WireCell::Vector(ray1.first.x() - center.x(), + ray1.first.y() - center.y(), + ray1.first.z() - center.z()); + dir_to_c2 = WireCell::Vector(test_p.x() - center.x(), + test_p.y() - center.y(), + test_p.z() - center.z()); + + double angle1 = std::acos(std::clamp(dir_to_c1.dot(dir1) / (dir_to_c1.magnitude() * dir1.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + double angle2 = std::acos(std::clamp(dir_to_c2.dot(dir3) / (dir_to_c2.magnitude() * dir3.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + if (angle1 > 25 || angle2 > 25) continue; + + } else { + center = WireCell::Point((p1_closest.x() + p2_closest.x()) / 2.0, + (p1_closest.y() + p2_closest.y()) / 2.0, + (p1_closest.z() + p2_closest.z()) / 2.0); + + auto [dis1, test_p] = shower_get_closest_point(*shower_1, center); + WireCell::Vector dir3 = shower_cal_dir_3vector(*shower_1, test_p, 15 * units::cm); + WireCell::Point p3(test_p.x() + dir3.x(), test_p.y() + dir3.y(), test_p.z() + dir3.z()); + WireCell::Ray ray3(test_p, p3); + + auto [new_p1, new_p2] = ray_closest_points(ray3, ray2); + center = new_p2; + + dir_to_c2 = WireCell::Vector(ray2.first.x() - center.x(), + ray2.first.y() - center.y(), + ray2.first.z() - center.z()); + dir_to_c1 = WireCell::Vector(test_p.x() - center.x(), + test_p.y() - center.y(), + test_p.z() - center.z()); + + double angle1 = std::acos(std::clamp(dir_to_c1.dot(dir3) / (dir_to_c1.magnitude() * dir3.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + double angle2 = std::acos(std::clamp(dir_to_c2.dot(dir2) / (dir_to_c2.magnitude() * dir2.magnitude()), -1.0, 1.0)) / M_PI * 180.0; + if (angle1 > 25 || angle2 > 25) continue; + } + + double angle = std::acos(std::clamp(dir_to_c1.dot(dir_to_c2) / (dir_to_c1.magnitude() * dir_to_c2.magnitude()), -1.0, 1.0)); + double mass_pio = std::sqrt(4 * shower_1->get_kine_charge() * shower_2->get_kine_charge() * + std::pow(std::sin(angle / 2.0), 2)); + map_shower_pair_mass_point[std::make_pair(shower_1, shower_2)] = std::make_pair(mass_pio, center); + + } else { + break; + } + } + } + + // Find best pi0 candidate + double mass_diff = 1e9; + double mass_save = 0; + ShowerPtr shower_1 = nullptr; + ShowerPtr shower_2 = nullptr; + double mass_offset = 10 * units::MeV; + WireCell::Point vtx_point; + + for (auto& [shower_pair, mass_point] : map_shower_pair_mass_point) { + if (std::abs(mass_point.first - 135 * units::MeV + mass_offset) < mass_diff) { + if (good_showers.find(shower_pair.first) == good_showers.end() && + good_showers.find(shower_pair.second) == good_showers.end()) continue; + + shower_1 = shower_pair.first; + shower_2 = shower_pair.second; + mass_diff = std::abs(mass_point.first - 135 * units::MeV + mass_offset); + mass_save = mass_point.first; + vtx_point = mass_point.second; + } + } + + // If found good pi0, update everything + if (mass_diff < 60 * units::MeV && shower_1 && shower_2) { + pi0_showers.insert(shower_1); + pi0_showers.insert(shower_2); + + int pio_id = acc_segment_id; + acc_segment_id++; + + map_shower_pio_id[shower_1] = pio_id; + map_shower_pio_id[shower_2] = pio_id; + map_pio_id_mass[pio_id] = std::make_pair(mass_save, 2); + map_pio_id_showers[pio_id].push_back(shower_1); + map_pio_id_showers[pio_id].push_back(shower_2); + + // Update main vertex position (hack) - set to reconstructed pi0 decay point + main_vertex->fit().point = vtx_point; + main_vertex->fit().dQ = 0; + + // Add other segments from main_vertex to showers + auto [start_vtx_1, conn_type_1] = shower_1->get_start_vertex_and_type(); + if (start_vtx_1 == main_vertex && conn_type_1 == 1) { + for (auto sg : map_vertex_segments[main_vertex]) { + if (sg == shower_1->start_segment()) continue; + shower_1->add_segment(sg); + } + } + + auto [start_vtx_2, conn_type_2] = shower_2->get_start_vertex_and_type(); + if (start_vtx_2 == main_vertex && conn_type_2 == 1) { + for (auto sg : map_vertex_segments[main_vertex]) { + if (sg == shower_2->start_segment()) continue; + shower_2->add_segment(sg); + } + } + + shower_1->set_start_vertex(main_vertex, 2); + shower_1->calculate_kinematics(particle_data, recomb_model); + + shower_2->set_start_vertex(main_vertex, 2); + shower_2->calculate_kinematics(particle_data, recomb_model); + + update_shower_maps(showers, map_vertex_in_shower, map_segment_in_shower, + map_vertex_to_shower, used_shower_clusters); + + std::cout << "Pi0 (displaced vertex) found with mass: " << mass_save / units::MeV + << " MeV with " << shower_1->get_kine_charge() / units::MeV << " MeV + " + << shower_2->get_kine_charge() / units::MeV << " MeV" << std::endl; + } + } +} + + +void PatternAlgorithms::shower_clustering_with_nv(int acc_segment_id, std::set& pi0_showers, std::map& map_shower_pio_id, std::map >& map_pio_id_showers, std::map >& map_pio_id_mass, std::map >& map_pio_id_saved_pair, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, Graph& graph, VertexPtr main_vertex, std::set& showers, Facade::Cluster* main_cluster, std::vector& other_clusters, std::map map_cluster_main_vertices, std::map& map_vertex_in_shower, std::map& map_segment_in_shower, std::map >& map_vertex_to_shower, std::set& used_shower_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model){ + + + + // Connect to the main cluster + shower_clustering_with_nv_in_main_cluster(graph, main_vertex, showers, + map_vertex_in_shower, map_segment_in_shower, + map_vertex_to_shower, used_shower_clusters, + vertices_in_long_muon, segments_in_long_muon); + + // Examine things connecting to the main vertex + shower_clustering_connecting_to_main_vertex(graph, main_vertex, showers, + map_vertex_in_shower, map_segment_in_shower, + map_vertex_to_shower, used_shower_clusters); + + // Shower clustering from main cluster + shower_clustering_with_nv_from_main_cluster(graph, main_vertex, main_cluster, showers, + map_vertex_in_shower, map_segment_in_shower, + map_vertex_to_shower, used_shower_clusters); + + // Shower clustering from vertices + shower_clustering_with_nv_from_vertices(graph, main_vertex, main_cluster, other_clusters, showers, + map_vertex_in_shower, map_segment_in_shower, + map_vertex_to_shower, used_shower_clusters, + vertices_in_long_muon, segments_in_long_muon, + track_fitter, dv, particle_data, recomb_model); + + // Calculate shower kinematics + calculate_shower_kinematics(showers, vertices_in_long_muon, segments_in_long_muon, + graph, track_fitter, dv, particle_data, recomb_model); + + // Examine and merge showers + examine_merge_showers(showers, main_vertex, map_vertex_in_shower, map_segment_in_shower, + map_vertex_to_shower, used_shower_clusters, + vertices_in_long_muon, segments_in_long_muon, + graph, track_fitter, dv, particle_data, recomb_model); + + // Check remaining clusters + shower_clustering_in_other_clusters(graph, main_vertex, showers, main_cluster, other_clusters, + map_cluster_main_vertices, map_vertex_in_shower, + map_segment_in_shower, map_vertex_to_shower, + used_shower_clusters, track_fitter, dv, + particle_data, recomb_model, true); + + // Calculate shower kinematics again + calculate_shower_kinematics(showers, vertices_in_long_muon, segments_in_long_muon, + graph, track_fitter, dv, particle_data, recomb_model); + + // Examine shower trunk and add to shower + examine_showers(graph, main_vertex, showers, main_cluster, other_clusters, + map_cluster_main_vertices, map_vertex_in_shower, map_segment_in_shower, + map_vertex_to_shower, used_shower_clusters, + track_fitter, dv, particle_data, recomb_model); + + // Identify pi0 with vertex + id_pi0_with_vertex(acc_segment_id, pi0_showers, map_shower_pio_id, map_pio_id_showers, map_pio_id_mass, + map_pio_id_saved_pair, graph, main_vertex, showers, main_cluster, + other_clusters, map_cluster_main_vertices, map_vertex_in_shower, + map_segment_in_shower, map_vertex_to_shower, used_shower_clusters, + track_fitter, dv, particle_data, recomb_model); + + // Identify pi0 without vertex (displaced vertex) + id_pi0_without_vertex(acc_segment_id, pi0_showers, map_shower_pio_id, map_pio_id_showers, + map_pio_id_mass, map_pio_id_saved_pair, graph, main_vertex, showers, + main_cluster, other_clusters, map_cluster_main_vertices, + map_vertex_in_shower, map_segment_in_shower, map_vertex_to_shower, + used_shower_clusters, track_fitter, dv, particle_data, recomb_model); +} \ No newline at end of file diff --git a/clus/src/NeutrinoStructureExaminer.cxx b/clus/src/NeutrinoStructureExaminer.cxx new file mode 100644 index 000000000..eeed84de5 --- /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 000000000..e69de29bb diff --git a/clus/src/NeutrinoTaggerNuE.cxx b/clus/src/NeutrinoTaggerNuE.cxx new file mode 100644 index 000000000..e69de29bb diff --git a/clus/src/NeutrinoTaggerNuMu.cxx b/clus/src/NeutrinoTaggerNuMu.cxx new file mode 100644 index 000000000..e69de29bb diff --git a/clus/src/NeutrinoTaggerPi0.cxx b/clus/src/NeutrinoTaggerPi0.cxx new file mode 100644 index 000000000..e69de29bb diff --git a/clus/src/NeutrinoTaggerSSM.cxx b/clus/src/NeutrinoTaggerSSM.cxx new file mode 100644 index 000000000..e69de29bb diff --git a/clus/src/NeutrinoTaggerSinglePhoton.cxx b/clus/src/NeutrinoTaggerSinglePhoton.cxx new file mode 100644 index 000000000..e69de29bb diff --git a/clus/src/NeutrinoTrackShowerSep.cxx b/clus/src/NeutrinoTrackShowerSep.cxx new file mode 100644 index 000000000..cecb237d7 --- /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 000000000..8f8e82ec8 --- /dev/null +++ b/clus/src/NeutrinoVertexFinder.cxx @@ -0,0 +1,3079 @@ +#include "WireCellClus/NeutrinoPatternBase.h" +#include "WireCellClus/PRSegmentFunctions.h" +#include "WireCellClus/FiducialUtils.h" +#include "WireCellClus/MyFCN.h" + +using namespace WireCell::Clus::PR; +using namespace WireCell::Clus; + +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; +} + +std::tuple PatternAlgorithms::examine_main_vertex_candidate(Graph& graph, VertexPtr vertex){ + bool flag_in = false; + int ntracks = 0; + int nshowers = 0; + SegmentPtr shower_cand = nullptr; + SegmentPtr track_cand = nullptr; + + // Get all segments connected to this vertex + if (!vertex || !vertex->descriptor_valid()) { + return std::make_tuple(flag_in, ntracks, nshowers); + } + + auto vd = vertex->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + + for (auto eit = edge_range.first; eit != edge_range.second; ++eit) { + SegmentPtr sg = graph[*eit].segment; + if (!sg) continue; + + // Check if segment is a shower (has kShowerTrajectory or kShowerTopology flags) + bool is_shower = sg->flags_any(SegmentFlags::kShowerTrajectory) || + sg->flags_any(SegmentFlags::kShowerTopology); + + if (is_shower) { + nshowers++; + shower_cand = sg; + } else { + ntracks++; + track_cand = sg; + } + + // Determine which end of segment connects to vertex + const auto& wcps = sg->wcpts(); + if (wcps.empty()) continue; + + bool flag_start = (ray_length(Ray{wcps.front().point, vertex->wcpt().point}) < + ray_length(Ray{wcps.back().point, vertex->wcpt().point})); + + // Check if segment is pointing IN to the vertex (strong direction) + int dir_sign = sg->dirsign(); + bool is_dir_weak = sg->dir_weak(); + + if (flag_start && dir_sign == -1 && !is_dir_weak) { + flag_in = true; + break; + } else if (!flag_start && dir_sign == 1 && !is_dir_weak) { + flag_in = true; + break; + } + } + + // Check Michel electron case: 2 segments (1 track + 1 shower) + int num_segments = 0; + auto edge_range2 = boost::out_edges(vd, graph); + for (auto eit = edge_range2.first; eit != edge_range2.second; ++eit) { + if (graph[*eit].segment) num_segments++; + } + + if (num_segments == 2 && ntracks == 1 && nshowers == 1 && track_cand && shower_cand) { + // Calculate the number of daughter showers + auto pair_result = calculate_num_daughter_showers(graph, vertex, shower_cand); + + if (pair_result.first <= 3 && pair_result.second < 30 * units::cm) { + const auto& track_wcps = track_cand->wcpts(); + if (!track_wcps.empty()) { + bool flag_start = (ray_length(Ray{track_wcps.front().point, vertex->wcpt().point}) < + ray_length(Ray{track_wcps.back().point, vertex->wcpt().point})); + + int track_dir = track_cand->dirsign(); + if ((flag_start && track_dir == -1) || (!flag_start && track_dir == 1)) { + flag_in = true; + } + } + } + } + + return std::make_tuple(flag_in, ntracks, nshowers); +} + +VertexPtr PatternAlgorithms::compare_main_vertices_all_showers(Graph& graph, Facade::Cluster& cluster, std::vector& vertex_candidates, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model){ + if (vertex_candidates.empty()) return nullptr; + + VertexPtr temp_main_vertex = vertex_candidates.front(); + + // Collect all points from segments and vertices in the cluster + std::vector pts; + + // Collect points from segments + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr sg = graph[*eit].segment; + if (!sg || sg->cluster() != &cluster) continue; + + const auto& wcpts = sg->wcpts(); + if (wcpts.size() <= 2) continue; + + for (size_t i = 1; i + 1 < wcpts.size(); i++) { + pts.push_back(wcpts[i].point); + } + } + + // Collect points from vertices + auto [vbegin, vend] = boost::vertices(graph); + for (auto vit = vbegin; vit != vend; ++vit) { + VertexPtr vtx = graph[*vit].vertex; + if (!vtx || vtx->cluster() != &cluster) continue; + pts.push_back(vtx->wcpt().point); + } + + if (pts.size() <= 3) { + return temp_main_vertex; + } + + // Calculate PCA main axis + auto pair_result = calc_PCA_main_axis(pts); + Facade::geo_vector_t dir = pair_result.second; + Facade::geo_point_t center = pair_result.first; + + // Find min and max vertices along the main axis + double min_val = 1e9, max_val = -1e9; + VertexPtr min_vtx = nullptr, max_vtx = nullptr; + + for (auto vtx : vertex_candidates) { + double val = (vtx->wcpt().point.x() - center.x()) * dir.x() + + (vtx->wcpt().point.y() - center.y()) * dir.y() + + (vtx->wcpt().point.z() - center.z()) * dir.z(); + + // Adjust for single short segment vertices + if (vtx->descriptor_valid()) { + auto vd = vtx->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + int num_segs = 0; + double seg_length = 0; + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + if (graph[*e_it].segment) { + num_segs++; + seg_length = segment_track_length(graph[*e_it].segment); + } + } + + if (num_segs == 1 && seg_length < 1 * units::cm) { + if (val > 0) val -= 0.5 * units::cm; + else if (val < 0) val += 0.5 * units::cm; + } + } + + if (val > max_val) { + max_val = val; + max_vtx = vtx; + } + if (val < min_val) { + min_val = val; + min_vtx = vtx; + } + } + + if (!min_vtx || !max_vtx || min_vtx == max_vtx) { + return temp_main_vertex; + } + + // Check if steiner point cloud exists + const auto& steiner_pc = cluster.get_pc("steiner_pc"); + if (steiner_pc.size() < 3) { + // Pick forward vertex based on z coordinate + if (max_vtx->wcpt().point.z() < min_vtx->wcpt().point.z()) { + temp_main_vertex = max_vtx; + } else { + temp_main_vertex = min_vtx; + } + return temp_main_vertex; + } + + // Find path between min and max vertices using steiner graph + auto path_points = do_rough_path(cluster, max_vtx->wcpt().point, min_vtx->wcpt().point); + + if (path_points.size() <= 2) { + // Pick forward vertex based on z coordinate + if (max_vtx->wcpt().point.z() < min_vtx->wcpt().point.z()) { + temp_main_vertex = max_vtx; + } else { + temp_main_vertex = min_vtx; + } + return temp_main_vertex; + } + + // Create temporary local graph for fitting + auto local_graph = std::make_shared(); + + // Create temporary vertices + auto tmp_v1 = make_vertex(*local_graph); + tmp_v1->wcpt().point = path_points.front(); + tmp_v1->cluster(&cluster); + + auto tmp_v2 = make_vertex(*local_graph); + tmp_v2->wcpt().point = path_points.back(); + tmp_v2->cluster(&cluster); + + // Create temporary segment + auto tmp_sg = create_segment_for_cluster(cluster, dv, path_points); + if (!tmp_sg) { + if (max_vtx->wcpt().point.z() < min_vtx->wcpt().point.z()) { + temp_main_vertex = max_vtx; + } else { + temp_main_vertex = min_vtx; + } + return temp_main_vertex; + } + + // Add segment to local graph + add_segment(*local_graph, tmp_sg, tmp_v1, tmp_v2); + + // Create local fitter with same configuration as input fitter + TrackFitting local_fitter(TrackFitting::FittingType::Multiple); + local_fitter.set_parameters(track_fitter.get_parameters()); + local_fitter.add_graph(local_graph); + + // Do fitting on local graph + local_fitter.do_multi_tracking(true, true, false); + + // Create fit point cloud + create_segment_fit_point_cloud(tmp_sg, dv, "fit"); + + // Associate points from cluster to segment + clustering_points_segments({tmp_sg}, dv, "associate_points", 0.5*units::cm, 3.0); + + // Determine shower direction + segment_determine_shower_direction(tmp_sg, particle_data, recomb_model, "associate_points"); + + double tmp_sg_length = segment_track_length(tmp_sg); + int tmp_sg_dir = tmp_sg->dirsign(); + + // Decide which vertex should be the main vertex based on direction + if (tmp_sg_dir == 1) { + temp_main_vertex = max_vtx; + } else if (tmp_sg_dir == -1) { + temp_main_vertex = min_vtx; + } else { + // No clear direction, pick forward vertex + if (max_vtx->wcpt().point.z() < min_vtx->wcpt().point.z()) { + temp_main_vertex = max_vtx; + } else { + temp_main_vertex = min_vtx; + } + } + + // For large showers, always pick forward vertex + if (tmp_sg_length > 80 * units::cm && + std::abs(max_vtx->wcpt().point.z() - min_vtx->wcpt().point.z()) > 40 * units::cm) { + if (max_vtx->wcpt().point.z() < min_vtx->wcpt().point.z()) { + temp_main_vertex = max_vtx; + } else { + temp_main_vertex = min_vtx; + } + } + + // Local graph and temporary elements automatically cleaned up when going out of scope + + return temp_main_vertex; +} + +float PatternAlgorithms::calc_conflict_maps(Graph& graph, VertexPtr vertex){ + // Assume temp_vertex is true neutrino vertex, calculate conflicts in the system + float num_conflicts = 0; + + // Map segment to its direction (start vertex -> end vertex) + std::map> map_seg_dir; + std::set used_vertices; + + if (!vertex || !vertex->descriptor_valid()) return num_conflicts; + + // Start from the assumed neutrino vertex + std::vector> segments_to_be_examined; + auto vd = vertex->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr seg = graph[*e_it].segment; + if (seg) { + segments_to_be_examined.push_back(std::make_pair(vertex, seg)); + } + } + used_vertices.insert(vertex); + + // Propagate through the graph and build direction map + while (!segments_to_be_examined.empty()) { + std::vector> temp_segments; + + for (const auto& [prev_vtx, current_sg] : segments_to_be_examined) { + // Skip if already examined + if (map_seg_dir.find(current_sg) != map_seg_dir.end()) continue; + + // Find the other vertex of this segment + VertexPtr curr_vertex = find_other_vertex(graph, current_sg, prev_vtx); + if (!curr_vertex) continue; + + // Record the direction: prev_vtx -> curr_vertex + map_seg_dir[current_sg] = std::make_pair(prev_vtx, curr_vertex); + + // Skip if we've already processed this vertex + if (used_vertices.find(curr_vertex) != used_vertices.end()) continue; + + // Add all segments connected to curr_vertex for examination + if (curr_vertex->descriptor_valid()) { + auto curr_vd = curr_vertex->get_descriptor(); + auto curr_edge_range = boost::out_edges(curr_vd, graph); + for (auto e_it = curr_edge_range.first; e_it != curr_edge_range.second; ++e_it) { + SegmentPtr seg = graph[*e_it].segment; + if (seg) { + temp_segments.push_back(std::make_pair(curr_vertex, seg)); + } + } + } + used_vertices.insert(curr_vertex); + } + segments_to_be_examined = temp_segments; + } + + // Check segments for direction conflicts + for (const auto& [sg, vtx_pair] : map_seg_dir) { + VertexPtr start_vtx = vtx_pair.first; + + // Determine which end of segment connects to start_vtx + const auto& wcps = sg->wcpts(); + if (wcps.empty()) continue; + + bool flag_start = (ray_length(Ray{wcps.front().point, start_vtx->wcpt().point}) < + ray_length(Ray{wcps.back().point, start_vtx->wcpt().point})); + + // Check if segment has direction and is long enough to matter + int dir_sign = sg->dirsign(); + bool is_shower = sg->flags_any(SegmentFlags::kShowerTrajectory) || + sg->flags_any(SegmentFlags::kShowerTopology); + double sg_length = segment_track_length(sg); + + if (dir_sign != 0 && ((is_shower && sg_length > 5*units::cm) || !is_shower)) { + // Check if direction conflicts with topology + if ((flag_start && dir_sign == -1) || (!flag_start && dir_sign == 1)) { + if (!sg->dir_weak()) { + num_conflicts += 1.0; + } else { + num_conflicts += 0.5; + } + } + } + } + + // Beam direction (along z-axis) + Facade::geo_vector_t dir_beam(0, 0, 1); + + // Check vertices for topology conflicts + for (VertexPtr vtx : used_vertices) { + if (!vtx->descriptor_valid()) continue; + + auto vtx_vd = vtx->get_descriptor(); + auto vtx_edge_range = boost::out_edges(vtx_vd, graph); + + // Count number of segments + int num_segments = 0; + for (auto e_it = vtx_edge_range.first; e_it != vtx_edge_range.second; ++e_it) { + if (graph[*e_it].segment) num_segments++; + } + if (num_segments <= 1) continue; + + int n_in = 0; + int n_in_shower = 0; + int n_out_tracks = 0; + int n_out_showers = 0; + + std::map map_in_segment_dirs; + std::map map_out_segment_dirs; + + // Analyze each segment connected to this vertex + for (auto e_it = vtx_edge_range.first; e_it != vtx_edge_range.second; ++e_it) { + SegmentPtr sg = graph[*e_it].segment; + if (!sg || map_seg_dir.find(sg) == map_seg_dir.end()) continue; + + VertexPtr start_vtx = map_seg_dir[sg].first; + bool is_shower = sg->flags_any(SegmentFlags::kShowerTrajectory) || + sg->flags_any(SegmentFlags::kShowerTopology); + // bool is_shower_traj = sg->flags_any(SegmentFlags::kShowerTrajectory); + + if (vtx != start_vtx) { + // Segment is incoming to this vertex + n_in++; + if (is_shower) n_in_shower++; + map_in_segment_dirs[sg] = segment_cal_dir_3vector(sg, vtx->wcpt().point, 10*units::cm); + } else { + // Segment is outgoing from this vertex + if (!is_shower) { + n_out_tracks++; + } else { + n_out_showers++; + } + map_out_segment_dirs[sg] = segment_cal_dir_3vector(sg, vtx->wcpt().point, 10*units::cm); + } + } + + // Check angles between incoming and outgoing segments + if (!map_in_segment_dirs.empty() && !map_out_segment_dirs.empty()) { + double max_angle = -1; + SegmentPtr sg1 = nullptr; + SegmentPtr sg2 = nullptr; + + for (const auto& [in_sg, in_dir] : map_in_segment_dirs) { + for (const auto& [out_sg, out_dir] : map_out_segment_dirs) { + double angle = std::acos(std::clamp(in_dir.dot(out_dir) / + (in_dir.magnitude() * out_dir.magnitude()), -1.0, 1.0)) + * 180.0 / M_PI; + if (angle > max_angle) { + max_angle = angle; + sg1 = in_sg; + sg2 = out_sg; + } + } + } + + if (sg1 && sg2) { + bool flag_check = true; + bool is_sg2_shower_traj = sg2->flags_any(SegmentFlags::kShowerTrajectory); + bool is_sg1_shower = sg1->flags_any(SegmentFlags::kShowerTrajectory) || + sg1->flags_any(SegmentFlags::kShowerTopology); + bool is_sg2_shower = sg2->flags_any(SegmentFlags::kShowerTrajectory) || + sg2->flags_any(SegmentFlags::kShowerTopology); + + // Skip check for shower trajectories or both showers + if (is_sg2_shower_traj || (is_sg1_shower && is_sg2_shower)) { + flag_check = false; + } + + double angle_beam = std::acos(std::clamp(map_in_segment_dirs[sg1].dot(dir_beam) / + map_in_segment_dirs[sg1].magnitude(), -1.0, 1.0)) + * 180.0 / M_PI; + + if (max_angle >= 0 && flag_check) { + if (max_angle < 35) { + num_conflicts += 5.0; + } else if (max_angle < 70) { + num_conflicts += 3.0; + } else if (max_angle < 85) { + num_conflicts += 1.0; + } else if (max_angle < 110) { + num_conflicts += 0.25; + } + + // Additional penalty for backward-going particles + if (angle_beam < 60 && max_angle < 110) { + num_conflicts += 1.0; + } else if (angle_beam < 45 && max_angle < 70) { + num_conflicts += 3.0; + } + } + } + } + + // Penalize multiple incoming particles + if (n_in > 1) { + if (n_in != n_in_shower) { + num_conflicts += (n_in - 1); + } else { + num_conflicts += (n_in - 1) / 2.0; + } + } + + // Penalize showers in with tracks out (suspicious topology) + if (n_in_shower > 0 && n_out_tracks > 0) { + num_conflicts += std::min(n_in_shower, n_out_tracks); + } + (void)n_out_showers; // to avoid unused variable warning + } + + return num_conflicts; +} + +VertexPtr PatternAlgorithms::compare_main_vertices(Graph& graph, Facade::Cluster& cluster, std::vector& vertex_candidates){ + if (vertex_candidates.empty()) return nullptr; + + std::map map_vertex_num; + for (auto vtx : vertex_candidates) { + map_vertex_num[vtx] = 0; + } + + // Find the longest muon candidate + SegmentPtr max_length_muon = nullptr; + double max_length = 0; + + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr sg = graph[*eit].segment; + if (!sg || sg->cluster() != &cluster) continue; + + // Skip showers + bool is_shower = sg->flags_any(SegmentFlags::kShowerTrajectory) || + sg->flags_any(SegmentFlags::kShowerTopology); + if (is_shower) continue; + + // Skip protons + if (sg->has_particle_info() && std::abs(sg->particle_info()->pdg()) == 2212) continue; + + double length = segment_track_length(sg); + if (length > max_length) { + max_length = length; + max_length_muon = sg; + } + } + + // Analyze proton topology for each vertex candidate + for (auto vtx : vertex_candidates) { + if (!vtx->descriptor_valid()) continue; + + int n_proton_in = 0; + int n_proton_out = 0; + + auto vd = vtx->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr sg = graph[*e_it].segment; + if (!sg) continue; + + bool is_proton = sg->has_particle_info() && std::abs(sg->particle_info()->pdg()) == 2212; + if (!is_proton) continue; + + int dir_sign = sg->dirsign(); + bool is_weak = sg->dir_weak(); + + if ((is_weak || dir_sign == 0)) { + VertexPtr other_vertex = find_other_vertex(graph, sg, vtx); + if (!other_vertex || !other_vertex->descriptor_valid()) continue; + + auto other_vd = other_vertex->get_descriptor(); + auto other_edge_range = boost::out_edges(other_vd, graph); + + int num_segs = 0; + for (auto oe_it = other_edge_range.first; oe_it != other_edge_range.second; ++oe_it) { + if (graph[*oe_it].segment) num_segs++; + } + + if (num_segs > 1) { + for (auto oe_it = other_edge_range.first; oe_it != other_edge_range.second; ++oe_it) { + SegmentPtr other_sg = graph[*oe_it].segment; + if (!other_sg) continue; + + const auto& wcps = other_sg->wcpts(); + if (wcps.empty()) continue; + + bool flag_start = (ray_length(Ray{wcps.front().point, other_vertex->wcpt().point}) < + ray_length(Ray{wcps.back().point, other_vertex->wcpt().point})); + + int other_dir = other_sg->dirsign(); + bool other_weak = other_sg->dir_weak(); + bool is_other_proton = other_sg->has_particle_info() && + std::abs(other_sg->particle_info()->pdg()) == 2212; + + if (!other_weak && is_other_proton) { + if ((flag_start && other_dir == 1) || (!flag_start && other_dir == -1)) { + n_proton_out++; + } + if ((flag_start && other_dir == -1) || (!flag_start && other_dir == 1)) { + n_proton_in++; + } + } + + if ((other_weak || other_dir == 0) && is_other_proton) { + n_proton_in++; + } + } + } + } + } + + // Score proton topology + if (n_proton_in > n_proton_out) { + map_vertex_num[vtx] -= (n_proton_in - n_proton_out) / 4.0; + } else { + map_vertex_num[vtx] -= (n_proton_in - n_proton_out) / 4.0 - (n_proton_in + n_proton_out) / 8.0; + } + } + + // Score based on z position (prefer forward/upstream vertices) + double min_z = 1e9; + for (auto vtx : vertex_candidates) { + if (vtx->wcpt().point.z() < min_z) min_z = vtx->wcpt().point.z(); + } + + for (auto vtx : vertex_candidates) { + if (!vtx->descriptor_valid()) continue; + + // Position penalty + map_vertex_num[vtx] -= (vtx->wcpt().point.z() - min_z) / (200 * units::cm); + + // Score based on connected segments + auto vd = vtx->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr sg = graph[*e_it].segment; + if (!sg) continue; + + bool is_shower = sg->flags_any(SegmentFlags::kShowerTrajectory) || + sg->flags_any(SegmentFlags::kShowerTopology); + // bool is_shower_traj = sg->flags_any(SegmentFlags::kShowerTrajectory); + + if (is_shower) { + map_vertex_num[vtx] += 1.0 / 4.0 / 2.0; // number of showers + + auto pair_results = calculate_num_daughter_showers(graph, vtx, sg); + if (pair_results.second > 45 * units::cm) { + map_vertex_num[vtx] += 1.0 / 4.0 / 2.0; + } + } else { + map_vertex_num[vtx] += 1.0 / 4.0; // number of tracks + } + + int dir_sign = sg->dirsign(); + bool is_weak = sg->dir_weak(); + bool is_proton = sg->has_particle_info() && std::abs(sg->particle_info()->pdg()) == 2212; + + if (is_proton && dir_sign != 0 && !is_weak) { + map_vertex_num[vtx] += 1.0 / 4.0; // has a clear proton + } else if (dir_sign != 0 && !is_shower) { + map_vertex_num[vtx] += 1.0 / 4.0 / 2.0; // has direction with track + } + + if (max_length > 35 * units::cm && sg == max_length_muon) { + map_vertex_num[vtx] += 1.0 / 4.0 / 2.0; // long muon adds weight + } + } + } + + // Score based on fiducial volume (removed offset_x in WCP, need to validate) + auto fiducial_utils = cluster.grouping()->get_fiducialutils(); + if (fiducial_utils) { + for (auto vtx : vertex_candidates) { + if (fiducial_utils->inside_fiducial_volume(vtx->wcpt().point)) { + map_vertex_num[vtx] += 0.5; // good - inside fiducial volume + } + } + } + + // Score based on topology conflicts + for (auto vtx : vertex_candidates) { + double num_conflicts = calc_conflict_maps(graph, vtx); + map_vertex_num[vtx] -= num_conflicts / 4.0; + } + + // Find the vertex with maximum score + double max_val = -1e9; + VertexPtr max_vertex = nullptr; + + for (auto vtx : vertex_candidates) { + if (map_vertex_num[vtx] > max_val) { + max_val = map_vertex_num[vtx]; + max_vertex = vtx; + } + } + + return max_vertex; +} + + +std::pair PatternAlgorithms::find_cont_muon_segment(Graph &graph, SegmentPtr sg, VertexPtr vtx, bool flag_ignore_dQ_dx){ + SegmentPtr sg1 = nullptr; + VertexPtr vtx1 = nullptr; + + double max_length = 0; + double max_angle = 0; + double max_ratio = 0; + + bool flag_cont = false; + + double max_ratio1 = 0; + double max_ratio1_length = 0; + + double sg_length = segment_track_length(sg); + + // Get vertex point + WireCell::Point vtx_point = vtx->fit().valid() ? vtx->fit().point : vtx->wcpt().point; + + if (!vtx->descriptor_valid()) { + return std::make_pair(sg1, vtx1); + } + + // Iterate through all segments connected to this vertex + auto vd = vtx->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr sg2 = graph[*e_it].segment; + if (!sg2 || sg2 == sg) continue; + + // Find the other vertex of sg2 + VertexPtr vtx2 = find_other_vertex(graph, sg2, vtx); + if (!vtx2) continue; + + // Calculate direction vectors at 15cm from vertex + Facade::geo_vector_t dir1 = segment_cal_dir_3vector(sg, vtx_point, 15*units::cm); + Facade::geo_vector_t dir2 = segment_cal_dir_3vector(sg2, vtx_point, 15*units::cm); + + if (dir1.magnitude() == 0 || dir2.magnitude() == 0) continue; + + double length = segment_track_length(sg2); + + // Calculate angle (180° - angle between directions) + double cos_angle = std::clamp(dir1.dot(dir2) / (dir1.magnitude() * dir2.magnitude()), -1.0, 1.0); + double angle = (M_PI - std::acos(cos_angle)) / M_PI * 180.0; + + // Calculate dQ/dx ratio + double ratio = segment_median_dQ_dx(sg2) / (43e3 / units::cm); + + // For longer segments, also check angle at 50cm + double angle1 = angle; + if (length > 50*units::cm) { + Facade::geo_vector_t dir3 = segment_cal_dir_3vector(sg, vtx_point, 50*units::cm); + Facade::geo_vector_t dir4 = segment_cal_dir_3vector(sg2, vtx_point, 50*units::cm); + + if (dir3.magnitude() > 0 && dir4.magnitude() > 0) { + double cos_angle1 = std::clamp(dir3.dot(dir4) / (dir3.magnitude() * dir4.magnitude()), -1.0, 1.0); + angle1 = (M_PI - std::acos(cos_angle1)) / M_PI * 180.0; + } + } + + // Check if this segment qualifies as a continuation + bool angle_ok = (angle < 10.0 || angle1 < 10.0 || + (sg_length < 6*units::cm && (angle < 15.0 || angle1 < 15.0))); + bool ratio_ok = (ratio < 1.3 || flag_ignore_dQ_dx); + + if (angle_ok && ratio_ok) { + flag_cont = true; + + // Select segment with maximum projected length + double projected_length = length * std::cos(angle / 180.0 * M_PI); + if (projected_length > max_length) { + max_length = projected_length; + max_angle = angle; + max_ratio = ratio; + sg1 = sg2; + vtx1 = vtx2; + } + } else { + // Track maximum dQ/dx ratio among non-qualifying segments + if (ratio > max_ratio1) { + max_ratio1 = ratio; + max_ratio1_length = length; + } + } + } + + (void)max_angle; + (void)max_ratio; + (void)max_ratio1_length; + + if (flag_cont) { + return std::make_pair(sg1, vtx1); + } else { + return std::make_pair(nullptr, nullptr); + } +} + +bool PatternAlgorithms::examine_direction(Graph& graph, VertexPtr vertex, VertexPtr main_vertex, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model, bool flag_final){ + if (!vertex || !vertex->cluster()) return false; + + Facade::Cluster& cluster = *vertex->cluster(); + + // Calculate cluster statistics + double max_vtx_length = 0; + double min_vtx_length = 1e9; + int num_total_segments = 0; + bool flag_only_showers = true; + + // Examine vertex segments to determine characteristics + if (vertex->descriptor_valid()) { + auto vd = vertex->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr seg = graph[*e_it].segment; + if (!seg) continue; + + double length = segment_track_length(seg); + if (length > max_vtx_length) max_vtx_length = length; + if (length < min_vtx_length) min_vtx_length = length; + } + } + + // Check all vertices in the cluster + auto [vbegin, vend] = boost::vertices(graph); + for (auto vit = vbegin; vit != vend; ++vit) { + VertexPtr vtx = graph[*vit].vertex; + if (!vtx || vtx->cluster() != &cluster) continue; + + auto results = examine_main_vertex_candidate(graph, vtx); + bool flag_in = std::get<0>(results); + int ntracks = std::get<1>(results); + // int nshowers = std::get<2>(results); + + if (!flag_in && ntracks > 0) { + flag_only_showers = false; + } + } + + // Count total segments in cluster + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr seg = graph[*eit].segment; + if (seg && seg->cluster() == &cluster) { + num_total_segments++; + } + } + + // Determine if only showers based on topology + if (vertex->descriptor_valid()) { + auto vd = vertex->get_descriptor(); + int num_vertex_segments = 0; + auto edge_range = boost::out_edges(vd, graph); + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + if (graph[*e_it].segment) num_vertex_segments++; + } + + if ((num_vertex_segments == 2 && (max_vtx_length > 30*units::cm || min_vtx_length > 15*units::cm)) || + (num_vertex_segments > 2 && num_total_segments > 4) || + (num_vertex_segments > 3)) { + flag_only_showers = false; + } + } + + // Beam direction (along z-axis) + Facade::geo_vector_t drift_dir(1, 0, 0); + + // Track used vertices and segments + std::set used_vertices; + std::set used_segments; + + // Start propagation from the main vertex + std::vector> segments_to_be_examined; + if (vertex->descriptor_valid()) { + auto vd = vertex->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr seg = graph[*e_it].segment; + if (seg) { + segments_to_be_examined.push_back(std::make_pair(vertex, seg)); + } + } + } + used_vertices.insert(vertex); + + // Propagate through the graph setting directions and particle types + while (!segments_to_be_examined.empty()) { + std::vector> temp_segments; + + for (const auto& [prev_vtx, current_sg] : segments_to_be_examined) { + if (!prev_vtx->descriptor_valid()) continue; + + // Check for incoming showers + bool flag_shower_in = false; + std::vector in_showers; + + auto prev_vd = prev_vtx->get_descriptor(); + auto prev_edge_range = boost::out_edges(prev_vd, graph); + for (auto e_it = prev_edge_range.first; e_it != prev_edge_range.second; ++e_it) { + SegmentPtr sg = graph[*e_it].segment; + if (!sg) continue; + + const auto& wcps = sg->wcpts(); + if (wcps.empty()) continue; + + bool flag_start = (ray_length(Ray{wcps.front().point, prev_vtx->wcpt().point}) < + ray_length(Ray{wcps.back().point, prev_vtx->wcpt().point})); + + int dir_sign = sg->dirsign(); + if ((flag_start && dir_sign == -1) || (!flag_start && dir_sign == 1)) { + if (sg->flags_any(SegmentFlags::kShowerTrajectory) || sg->flags_any(SegmentFlags::kShowerTopology)) { + flag_shower_in = true; + in_showers.push_back(sg); + break; + } + } + } + + if (used_segments.find(current_sg) != used_segments.end()) continue; + + double length = segment_track_length(current_sg); + bool is_shower = current_sg->flags_any(SegmentFlags::kShowerTrajectory) || + current_sg->flags_any(SegmentFlags::kShowerTopology); + + // Determine segment direction + if (current_sg->dirsign() == 0 || current_sg->dir_weak() || is_shower || flag_final) { + const auto& wcps = current_sg->wcpts(); + if (!wcps.empty()) { + bool flag_start = (ray_length(Ray{wcps.front().point, prev_vtx->wcpt().point}) < + ray_length(Ray{wcps.back().point, prev_vtx->wcpt().point})); + + // Set direction + if (flag_start) { + current_sg->dirsign(1); + } else { + current_sg->dirsign(-1); + } + + // Determine particle type + if (flag_shower_in && current_sg->dirsign() == 0 && !is_shower) { + auto four_momentum = segment_cal_4mom(current_sg, 11, particle_data, recomb_model); + auto pinfo = std::make_shared(11, particle_data->get_particle_mass(11), particle_data->pdg_to_name(11), four_momentum); + current_sg->particle_info(pinfo); + } else if (flag_shower_in && length < 2.0*units::cm && !is_shower) { + auto four_momentum = segment_cal_4mom(current_sg, 11, particle_data, recomb_model); + auto pinfo = std::make_shared(11, particle_data->get_particle_mass(11), particle_data->pdg_to_name(11), four_momentum); + current_sg->particle_info(pinfo); + } else if (flag_shower_in && current_sg->has_particle_info() && + (std::abs(current_sg->particle_info()->pdg()) == 13 || current_sg->particle_info()->pdg() == 0)) { + auto four_momentum = segment_cal_4mom(current_sg, 11, particle_data, recomb_model); + auto pinfo = std::make_shared(11, particle_data->get_particle_mass(11), particle_data->pdg_to_name(11), four_momentum); + current_sg->particle_info(pinfo); + } else { + auto pair_result = calculate_num_daughter_showers(graph, prev_vtx, current_sg); + auto pair_result1 = calculate_num_daughter_showers(graph, prev_vtx, current_sg, false); + int num_daughter_showers = pair_result.first; + double length_daughter_showers = pair_result.second; + + // Check if should be electron based on daughter showers + int current_pdg = current_sg->has_particle_info() ? current_sg->particle_info()->pdg() : 0; + if (current_pdg != 11 && + (num_daughter_showers >= 4 || + (length_daughter_showers > 50*units::cm && num_daughter_showers >= 2)) && + pair_result.second > pair_result1.second - length - pair_result.second) { + + // Check angles with connected segments + bool flag_change = false; + VertexPtr next_vertex = find_other_vertex(graph, current_sg, prev_vtx); + if (next_vertex && next_vertex->descriptor_valid()) { + Facade::geo_vector_t tmp_dir1 = segment_cal_dir_3vector(current_sg, next_vertex->wcpt().point, 15*units::cm); + + auto next_vd = next_vertex->get_descriptor(); + auto next_edge_range = boost::out_edges(next_vd, graph); + for (auto ne_it = next_edge_range.first; ne_it != next_edge_range.second; ++ne_it) { + SegmentPtr other_sg = graph[*ne_it].segment; + if (!other_sg || other_sg == current_sg) continue; + + Facade::geo_vector_t tmp_dir2 = segment_cal_dir_3vector(other_sg, next_vertex->wcpt().point, 15*units::cm); + + if (tmp_dir1.magnitude() > 0 && tmp_dir2.magnitude() > 0) { + double angle = std::acos(std::clamp(tmp_dir1.dot(tmp_dir2) / + (tmp_dir1.magnitude() * tmp_dir2.magnitude()), -1.0, 1.0)) + * 180.0 / M_PI; + double angle_drift1 = std::acos(std::clamp(drift_dir.dot(tmp_dir1) / + (drift_dir.magnitude() * tmp_dir1.magnitude()), -1.0, 1.0)) + * 180.0 / M_PI; + double angle_drift2 = std::acos(std::clamp(drift_dir.dot(tmp_dir2) / + (drift_dir.magnitude() * tmp_dir2.magnitude()), -1.0, 1.0)) + * 180.0 / M_PI; + double other_length = segment_track_length(other_sg); + + if (angle > 155 || + (angle > 135 && std::abs(angle_drift1 - 90) < 10 && std::abs(angle_drift2 - 90) < 10) || + (angle > 135 && other_length < 6*units::cm)) { + flag_change = true; + break; + } + } + } + } + + if (flag_change) { + auto four_momentum = segment_cal_4mom(current_sg, 11, particle_data, recomb_model); + auto pinfo = std::make_shared(11, particle_data->get_particle_mass(11), particle_data->pdg_to_name(11), four_momentum); + current_sg->particle_info(pinfo); + } + } else if (current_pdg == 11 && num_daughter_showers <= 2 && !flag_shower_in && + !current_sg->flags_any(SegmentFlags::kShowerTopology) && + !current_sg->flags_any(SegmentFlags::kShowerTrajectory) && + length > 10*units::cm && !flag_only_showers) { + + double direct_length = segment_track_direct_length(current_sg); + if (direct_length >= 34*units::cm || + (direct_length < 34*units::cm && direct_length > 0.93 * length)) { + auto four_momentum = segment_cal_4mom(current_sg, 13, particle_data, recomb_model); + auto pinfo = std::make_shared(13, particle_data->get_particle_mass(13), particle_data->pdg_to_name(13), four_momentum); + current_sg->particle_info(pinfo); + } + } else if (current_pdg == 11 && current_sg->flags_any(SegmentFlags::kShowerTrajectory) && + num_daughter_showers == 1 && !flag_only_showers) { + auto pair_result1 = calculate_num_daughter_showers(graph, prev_vtx, current_sg, false); + if (pair_result1.second > 3*length && pair_result1.second - length > 12*units::cm) { + current_sg->unset_flags(SegmentFlags::kShowerTrajectory); + auto four_momentum = segment_cal_4mom(current_sg, 13, particle_data, recomb_model); + auto pinfo = std::make_shared(13, particle_data->get_particle_mass(13), particle_data->pdg_to_name(13), four_momentum); + current_sg->particle_info(pinfo); + } + } + } + + // Default particle type assignment for undetermined particles from main vertex + if (vertex == main_vertex) { + int current_pdg = current_sg->has_particle_info() ? current_sg->particle_info()->pdg() : 0; + if (current_pdg == 0 && !is_shower) { + if (flag_only_showers) { + auto four_momentum = segment_cal_4mom(current_sg, 11, particle_data, recomb_model); + auto pinfo = std::make_shared(11, particle_data->get_particle_mass(11), particle_data->pdg_to_name(11), four_momentum); + current_sg->particle_info(pinfo); + } else { + double dqdx_ratio = segment_median_dQ_dx(current_sg) / (43e3 / units::cm); + if (dqdx_ratio > 1.4) { + auto four_momentum = segment_cal_4mom(current_sg, 2212, particle_data, recomb_model); + auto pinfo = std::make_shared(2212, particle_data->get_particle_mass(2212), particle_data->pdg_to_name(2212), four_momentum); + current_sg->particle_info(pinfo); + } else { + auto four_momentum = segment_cal_4mom(current_sg, 13, particle_data, recomb_model); + auto pinfo = std::make_shared(13, particle_data->get_particle_mass(13), particle_data->pdg_to_name(13), four_momentum); + current_sg->particle_info(pinfo); + } + } + } + } + + current_sg->dir_weak(true); + } + } else if (current_sg->dirsign() != 0 && !current_sg->dir_weak()) { + // Strong direction already set + auto pair_result = calculate_num_daughter_showers(graph, prev_vtx, current_sg); + int num_daughter_showers = pair_result.first; + + int current_pdg = current_sg->has_particle_info() ? current_sg->particle_info()->pdg() : 0; + if (current_pdg == 2212 && flag_shower_in && num_daughter_showers == 0) { + for (auto in_shower : in_showers) { + double dqdx_ratio = segment_median_dQ_dx(in_shower) / (43e3 / units::cm); + if (dqdx_ratio > 1.3) { + auto four_momentum = segment_cal_4mom(in_shower, 2212, particle_data, recomb_model); + auto pinfo = std::make_shared(2212, particle_data->get_particle_mass(2212), particle_data->pdg_to_name(2212), four_momentum); + in_shower->particle_info(pinfo); + } else { + auto four_momentum = segment_cal_4mom(in_shower, 211, particle_data, recomb_model); + auto pinfo = std::make_shared(211, particle_data->get_particle_mass(211), particle_data->pdg_to_name(211), four_momentum); + in_shower->particle_info(pinfo); + } + in_shower->unset_flags(SegmentFlags::kShowerTrajectory); + in_shower->unset_flags(SegmentFlags::kShowerTopology); + } + } + } + + used_segments.insert(current_sg); + + // Find next vertex and add its segments to examination list + VertexPtr curr_vertex = find_other_vertex(graph, current_sg, prev_vtx); + if (!curr_vertex || used_vertices.find(curr_vertex) != used_vertices.end()) continue; + + if (curr_vertex->descriptor_valid()) { + auto curr_vd = curr_vertex->get_descriptor(); + auto curr_edge_range = boost::out_edges(curr_vd, graph); + for (auto ce_it = curr_edge_range.first; ce_it != curr_edge_range.second; ++ce_it) { + SegmentPtr seg = graph[*ce_it].segment; + if (seg) { + temp_segments.push_back(std::make_pair(curr_vertex, seg)); + } + } + } + used_vertices.insert(curr_vertex); + } + segments_to_be_examined = temp_segments; + } + + // Find long muon candidates + bool flag_fill_long_muon = true; + for (auto seg : segments_in_long_muon) { + if (seg->cluster() == &cluster) { + flag_fill_long_muon = false; + break; + } + } + + if (flag_fill_long_muon && vertex->descriptor_valid()) { + auto vd = vertex->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr sg = graph[*e_it].segment; + if (!sg) continue; + + double dqdx_ratio = segment_median_dQ_dx(sg) / (43e3 / units::cm); + if (dqdx_ratio > 1.3) continue; + + VertexPtr vtx = find_other_vertex(graph, sg, vertex); + if (!vtx) continue; + + std::vector acc_segments; + std::vector acc_vertices; + acc_segments.push_back(sg); + acc_vertices.push_back(vtx); + + auto results = find_cont_muon_segment(graph, sg, vtx); + while (results.first != nullptr) { + acc_segments.push_back(results.first); + acc_vertices.push_back(results.second); + results = find_cont_muon_segment(graph, results.first, results.second); + } + + double total_length = 0, max_length = 0; + for (auto acc_seg : acc_segments) { + double length = segment_track_length(acc_seg); + total_length += length; + if (length > max_length) max_length = length; + } + + if (total_length > 45*units::cm && max_length > 35*units::cm && acc_segments.size() > 1) { + for (auto acc_seg : acc_segments) { + auto four_momentum = segment_cal_4mom(acc_seg, 13, particle_data, recomb_model); + auto pinfo = std::make_shared(13, particle_data->get_particle_mass(13), particle_data->pdg_to_name(13), four_momentum); + acc_seg->particle_info(pinfo); + acc_seg->unset_flags(SegmentFlags::kShowerTrajectory); + acc_seg->unset_flags(SegmentFlags::kShowerTopology); + segments_in_long_muon.insert(acc_seg); + } + for (auto acc_vtx : acc_vertices) { + vertices_in_long_muon.insert(acc_vtx); + } + } + } + } + + // Find muon candidate and make others pions + if (vertex->descriptor_valid()) { + SegmentPtr muon_sg = nullptr; + double muon_length = 0; + std::vector pion_sgs; + + auto vd = vertex->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr sg = graph[*e_it].segment; + if (!sg) continue; + + int pdg = sg->has_particle_info() ? sg->particle_info()->pdg() : 0; + if (std::abs(pdg) == 13) { + if (segments_in_long_muon.find(sg) != segments_in_long_muon.end()) continue; + + VertexPtr other_vertex = find_other_vertex(graph, sg, vertex); + if (!other_vertex || !other_vertex->descriptor_valid()) continue; + + int n_proton = 0; + auto other_vd = other_vertex->get_descriptor(); + auto other_edge_range = boost::out_edges(other_vd, graph); + for (auto oe_it = other_edge_range.first; oe_it != other_edge_range.second; ++oe_it) { + SegmentPtr other_sg = graph[*oe_it].segment; + if (other_sg && other_sg->has_particle_info() && + std::abs(other_sg->particle_info()->pdg()) == 2212) { + n_proton++; + } + } + + double sg_length = segment_track_length(sg); + if (sg_length > muon_length && n_proton == 0) { + muon_length = sg_length; + muon_sg = sg; + } + pion_sgs.push_back(sg); + } else if (pdg == 0) { + VertexPtr other_vertex = find_other_vertex(graph, sg, vertex); + if (!other_vertex || !other_vertex->descriptor_valid()) continue; + + int n_proton = 0; + auto other_vd = other_vertex->get_descriptor(); + auto other_edge_range = boost::out_edges(other_vd, graph); + for (auto oe_it = other_edge_range.first; oe_it != other_edge_range.second; ++oe_it) { + SegmentPtr other_sg = graph[*oe_it].segment; + if (other_sg && other_sg->has_particle_info() && + std::abs(other_sg->particle_info()->pdg()) == 2212) { + n_proton++; + } + } + + if (n_proton > 0) { + double dqdx_ratio = segment_median_dQ_dx(sg) / (43e3 / units::cm); + if (dqdx_ratio > 1.3) { + auto four_momentum = segment_cal_4mom(sg, 2212, particle_data, recomb_model); + auto pinfo = std::make_shared(2212, particle_data->get_particle_mass(2212), particle_data->pdg_to_name(2212), four_momentum); + sg->particle_info(pinfo); + } else { + auto four_momentum = segment_cal_4mom(sg, 211, particle_data, recomb_model); + auto pinfo = std::make_shared(211, particle_data->get_particle_mass(211), particle_data->pdg_to_name(211), four_momentum); + sg->particle_info(pinfo); + } + } + } + } + + // Convert non-muon candidates to pions + for (auto pion_sg : pion_sgs) { + if (pion_sg == muon_sg) continue; + auto four_momentum = segment_cal_4mom(pion_sg, 211, particle_data, recomb_model); + auto pinfo = std::make_shared(211, particle_data->get_particle_mass(211), particle_data->pdg_to_name(211), four_momentum); + pion_sg->particle_info(pinfo); + } + } + + // Find Michel electrons + auto [ebegin2, eend2] = boost::edges(graph); + for (auto eit = ebegin2; eit != eend2; ++eit) { + SegmentPtr sg = graph[*eit].segment; + if (!sg || sg->cluster() != &cluster) continue; + + // Check if segment has particle info with mass but no 4-momentum yet, and is not shower topology + bool has_4mom = sg->has_particle_info(); + if (has_4mom && sg->particle_info()->mass() > 0 && + !sg->flags_any(SegmentFlags::kShowerTopology)) { + + if (!sg->dir_weak()) { + // Strong direction - calculate 4-momentum + int pdg = sg->particle_info()->pdg(); + auto four_momentum = segment_cal_4mom(sg, pdg, particle_data, recomb_model); + auto pinfo = std::make_shared(pdg, particle_data->get_particle_mass(pdg), particle_data->pdg_to_name(pdg), four_momentum); + sg->particle_info(pinfo); + } else { + // Weak direction - need to check endpoint conditions + // Find the two vertices of this segment + VertexPtr start_v = nullptr, end_v = nullptr; + + auto [vbegin, vend] = boost::vertices(graph); + for (auto vit = vbegin; vit != vend; ++vit) { + VertexPtr vtx = graph[*vit].vertex; + if (!vtx || !vtx->descriptor_valid()) continue; + + // Check if this vertex is connected to our segment + auto vtx_vd = vtx->get_descriptor(); + auto vtx_edge_range = boost::out_edges(vtx_vd, graph); + for (auto ve_it = vtx_edge_range.first; ve_it != vtx_edge_range.second; ++ve_it) { + if (graph[*ve_it].segment == sg) { + // This vertex is connected to our segment + const auto& wcps = sg->wcpts(); + if (!wcps.empty()) { + if (ray_length(Ray{wcps.front().point, vtx->wcpt().point}) < 0.01*units::cm) { + start_v = vtx; + } else if (ray_length(Ray{wcps.back().point, vtx->wcpt().point}) < 0.01*units::cm) { + end_v = vtx; + } + } + break; + } + } + if (start_v && end_v) break; + } + + if (!start_v || !end_v) continue; + + int dir_sign = sg->dirsign(); + auto fiducial_utils = cluster.grouping()->get_fiducialutils(); + + // Count segments at start and end vertices + int num_segs_start = 0, num_segs_end = 0; + if (start_v->descriptor_valid()) { + auto start_vd = start_v->get_descriptor(); + auto start_edge_range = boost::out_edges(start_vd, graph); + for (auto se_it = start_edge_range.first; se_it != start_edge_range.second; ++se_it) { + if (graph[*se_it].segment) num_segs_start++; + } + } + if (end_v->descriptor_valid()) { + auto end_vd = end_v->get_descriptor(); + auto end_edge_range = boost::out_edges(end_vd, graph); + for (auto ee_it = end_edge_range.first; ee_it != end_edge_range.second; ++ee_it) { + if (graph[*ee_it].segment) num_segs_end++; + } + } + + // Check if endpoint is in fiducial volume or is Michel electron candidate + WireCell::Point end_pt = end_v->fit().valid() ? end_v->fit().point : end_v->wcpt().point; + WireCell::Point start_pt = start_v->fit().valid() ? start_v->fit().point : start_v->wcpt().point; + + bool should_calc = false; + + // Case 1: Direction is outward and endpoint is isolated in fiducial volume + if (dir_sign == 1 && num_segs_end == 1 && fiducial_utils && + fiducial_utils->inside_fiducial_volume(end_pt)) { + should_calc = true; + } else if (dir_sign == -1 && num_segs_start == 1 && fiducial_utils && + fiducial_utils->inside_fiducial_volume(start_pt)) { + should_calc = true; + } + // Case 2: Check for Michel electron topology at end vertex (2 segments, one is shower) + else if (num_segs_end == 2 && end_v->descriptor_valid()) { + bool flag_Michel = false; + auto end_vd = end_v->get_descriptor(); + auto end_edge_range = boost::out_edges(end_vd, graph); + for (auto ee_it = end_edge_range.first; ee_it != end_edge_range.second; ++ee_it) { + SegmentPtr other_sg = graph[*ee_it].segment; + if (!other_sg || other_sg == sg) continue; + // Check if other segment is a shower (kShowerTrajectory flag or electron PDG) + if (other_sg->flags_any(SegmentFlags::kShowerTrajectory) || + (other_sg->has_particle_info() && std::abs(other_sg->particle_info()->pdg()) == 11)) { + flag_Michel = true; + break; + } + } + if (flag_Michel) should_calc = true; + } + // Case 3: Check for Michel electron topology at start vertex (2 segments, one is shower) + else if (num_segs_start == 2 && start_v->descriptor_valid()) { + bool flag_Michel = false; + auto start_vd = start_v->get_descriptor(); + auto start_edge_range = boost::out_edges(start_vd, graph); + for (auto se_it = start_edge_range.first; se_it != start_edge_range.second; ++se_it) { + SegmentPtr other_sg = graph[*se_it].segment; + if (!other_sg || other_sg == sg) continue; + // Check if other segment is a shower (kShowerTrajectory flag or electron PDG) + if (other_sg->flags_any(SegmentFlags::kShowerTrajectory) || + (other_sg->has_particle_info() && std::abs(other_sg->particle_info()->pdg()) == 11)) { + flag_Michel = true; + break; + } + } + if (flag_Michel) should_calc = true; + } + + if (should_calc) { + int pdg = sg->particle_info()->pdg(); + auto four_momentum = segment_cal_4mom(sg, pdg, particle_data, recomb_model); + auto pinfo = std::make_shared(pdg, particle_data->get_particle_mass(pdg), particle_data->pdg_to_name(pdg), four_momentum); + sg->particle_info(pinfo); + } + } + } + } + + return examine_maps(graph, cluster); +} + +bool PatternAlgorithms::eliminate_short_vertex_activities(Graph& graph, Facade::Cluster& cluster, VertexPtr main_vertex, std::set& existing_segments, TrackFitting& track_fitter, IDetectorVolumes::pointer dv){ + bool flag_updated = false; + bool flag_continue = true; + + while (flag_continue) { + flag_continue = false; + std::set to_be_removed_segments; + std::set to_be_removed_vertices; + + // Iterate through all edges (segments) in the graph + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr sg = graph[*eit].segment; + if (!sg || sg->cluster() != &cluster) continue; + if (existing_segments.find(sg) != existing_segments.end()) continue; + + // Get the two vertices of this segment + auto [vbegin, vend] = boost::vertices(graph); + VertexPtr v1 = nullptr, v2 = nullptr; + + // Find vertices connected to this segment + for (auto vit = vbegin; vit != vend; ++vit) { + VertexPtr vtx = graph[*vit].vertex; + if (!vtx || !vtx->descriptor_valid()) continue; + + auto vtx_vd = vtx->get_descriptor(); + auto vtx_edge_range = boost::out_edges(vtx_vd, graph); + for (auto ve_it = vtx_edge_range.first; ve_it != vtx_edge_range.second; ++ve_it) { + if (graph[*ve_it].segment == sg) { + if (!v1) v1 = vtx; + else if (!v2 && vtx != v1) v2 = vtx; + break; + } + } + if (v1 && v2) break; + } + + if (!v1 || !v2) continue; + + // Count segments at each vertex + int num_segs_v1 = 0, num_segs_v2 = 0; + if (v1->descriptor_valid()) { + auto v1d = v1->get_descriptor(); + auto v1_edge_range = boost::out_edges(v1d, graph); + for (auto ve_it = v1_edge_range.first; ve_it != v1_edge_range.second; ++ve_it) { + if (graph[*ve_it].segment) num_segs_v1++; + } + } + if (v2->descriptor_valid()) { + auto v2d = v2->get_descriptor(); + auto v2_edge_range = boost::out_edges(v2d, graph); + for (auto ve_it = v2_edge_range.first; ve_it != v2_edge_range.second; ++ve_it) { + if (graph[*ve_it].segment) num_segs_v2++; + } + } + + double length = segment_track_direct_length(sg); + + // Check Case 1: v1 has 1 segment, v2 has >=3 segments + if (num_segs_v1 == 1 && num_segs_v2 >= 3) { + if (length < 0.36*units::cm) { + to_be_removed_segments.insert(sg); + to_be_removed_vertices.insert(v1); + flag_continue = true; + break; + } else if (length < 0.5*units::cm && num_segs_v2 > 3) { + to_be_removed_segments.insert(sg); + to_be_removed_vertices.insert(v1); + flag_continue = true; + break; + } + } + // Check Case 2: v2 has 1 segment, v1 has >=3 segments + else if (num_segs_v2 == 1 && num_segs_v1 >= 3) { + if (length < 0.36*units::cm) { + to_be_removed_segments.insert(sg); + to_be_removed_vertices.insert(v2); + flag_continue = true; + break; + } else if (length < 0.5*units::cm && num_segs_v1 > 3) { + to_be_removed_segments.insert(sg); + to_be_removed_vertices.insert(v2); + flag_continue = true; + break; + } + } + + // Check Case 3: Very short segments (< 0.1 cm) or segments connected to main_vertex + if (!flag_continue) { + if ((v1 == main_vertex && num_segs_v1 > 1) || (v2 == main_vertex && num_segs_v2 > 1)) { + if (length < 0.1*units::cm) { + to_be_removed_segments.insert(sg); + to_be_removed_vertices.insert(v2); + flag_continue = true; + break; + } + } + } + + // Check Case 4: Isolated vertex close to another segment + if (!flag_continue) { + WireCell::Point v1_pt = v1->fit().valid() ? v1->fit().point : v1->wcpt().point; + WireCell::Point v2_pt = v2->fit().valid() ? v2->fit().point : v2->wcpt().point; + // auto v1_wpid = dv->contained_by(v1_pt); + // auto v2_wpid = dv->contained_by(v2_pt); + + if (num_segs_v1 == 1 && num_segs_v2 > 1 && v2->descriptor_valid()) { + auto v2d = v2->get_descriptor(); + auto v2_edge_range = boost::out_edges(v2d, graph); + for (auto ve_it = v2_edge_range.first; ve_it != v2_edge_range.second; ++ve_it) { + SegmentPtr sg1 = graph[*ve_it].segment; + if (!sg1 || sg1 == sg) continue; + + auto [dis, closest_pt] = segment_get_closest_point(sg1, v1_pt, "fit"); + double seg_length = segment_track_length(sg); + + if (dis < 0.36*units::cm) { + to_be_removed_segments.insert(sg); + to_be_removed_vertices.insert(v1); + flag_continue = true; + break; + } else if ((v2 == main_vertex && dis < 0.45*units::cm && seg_length < 0.45*units::cm)) { + to_be_removed_segments.insert(sg); + to_be_removed_vertices.insert(v1); + flag_continue = true; + break; + } + } + } else if (num_segs_v2 == 1 && num_segs_v1 > 1 && v1->descriptor_valid()) { + auto v1d = v1->get_descriptor(); + auto v1_edge_range = boost::out_edges(v1d, graph); + for (auto ve_it = v1_edge_range.first; ve_it != v1_edge_range.second; ++ve_it) { + SegmentPtr sg1 = graph[*ve_it].segment; + if (!sg1 || sg1 == sg) continue; + + auto [dis, closest_pt] = segment_get_closest_point(sg1, v2_pt, "fit"); + double seg_length = segment_track_length(sg); + + if (dis < 0.36*units::cm) { + to_be_removed_segments.insert(sg); + to_be_removed_vertices.insert(v2); + flag_continue = true; + break; + } else if ((v1 == main_vertex && dis < 0.45*units::cm && seg_length < 0.45*units::cm)) { + to_be_removed_segments.insert(sg); + to_be_removed_vertices.insert(v2); + flag_continue = true; + break; + } + } + } + } + + // Check Case 5: Segment not in existing_segments and all points close to existing segments + if (!flag_continue && existing_segments.find(sg) == existing_segments.end() && length > 0.45*units::cm) { + const auto& wcpts = sg->wcpts(); + int n_good = 0; + + for (size_t i = 0; i < wcpts.size(); i++) { + WireCell::Point pt = wcpts[i].point; + auto wpid = dv->contained_by(pt); + if (wpid.face() == -1 || wpid.apa() == -1) continue; + + double dis_u = 1e9, dis_v = 1e9, dis_w = 1e9; + + for (auto existing_sg : existing_segments) { + // Check if segment exists in graph + bool seg_exists = false; + auto [ebegin2, eend2] = boost::edges(graph); + for (auto eit2 = ebegin2; eit2 != eend2; ++eit2) { + if (graph[*eit2].segment == existing_sg) { + seg_exists = true; + break; + } + } + if (!seg_exists) continue; + + auto [dist_u, dist_v, dist_w] = segment_get_closest_2d_distances(existing_sg, pt, wpid.apa(), wpid.face(), "fit"); + if (dist_u < dis_u) dis_u = dist_u; + if (dist_v < dis_v) dis_v = dist_v; + if (dist_w < dis_w) dis_w = dist_w; + } + + if ((dis_u > 0.45*units::cm || dis_v > 0.45*units::cm || dis_w > 0.45*units::cm)) { + n_good++; + } + } + + if (n_good == 0) { + to_be_removed_segments.insert(sg); + if (num_segs_v1 == 1) to_be_removed_vertices.insert(v1); + if (num_segs_v2 == 1) to_be_removed_vertices.insert(v2); + } + } + + if (flag_continue) break; + } + + // Remove segments and vertices + for (auto sg : to_be_removed_segments) { + flag_updated = true; + remove_segment(graph, sg); + } + for (auto vtx : to_be_removed_vertices) { + remove_vertex(graph, vtx); + } + } + + return flag_updated; +} + + +bool PatternAlgorithms::fit_vertex(Facade::Cluster& cluster, VertexPtr vertex, VertexPtr main_vertex, std::set& sg_set, TrackFitting& track_fitter, IDetectorVolumes::pointer dv){ + // Allow to move 1.5 cm - create MyFCN object with constraint parameters + MyFCN fcn(vertex, true, 0.43*units::cm, 1.5*units::cm, 0.9*units::cm, 6*units::cm); + + // Add all segments to the fitting + for (auto it = sg_set.begin(); it != sg_set.end(); it++) { + fcn.AddSegment(*it); + } + + // If this is the main vertex, enforce two track fit + if (vertex == main_vertex) fcn.set_enforce_two_track_fit(true); + + // Perform vertex fitting + std::pair results = fcn.FitVertex(); + + // Get grouping for charge calculation + auto grouping = cluster.grouping(); + if (!grouping) { + if (results.first) + fcn.UpdateInfo(results.second, cluster, track_fitter, dv); + return results.first; + } + + // Get transform for coordinate conversion + const auto transform = track_fitter.get_pc_transforms()->pc_transform( + cluster.get_scope_transform(cluster.get_default_scope())); + // double cluster_t0 = cluster.get_cluster_t0(); + + // Get old and new vertex positions + Facade::geo_point_t old_pos = vertex->fit().point; + Facade::geo_point_t new_pos = results.second; + + // Get APA/face for old and new positions + auto old_wpid = dv->contained_by(old_pos); + auto new_wpid = dv->contained_by(new_pos); + + // Calculate average charge at old and new positions + double old_charge = 0; + double new_charge = 0; + + if (old_wpid.apa() != -1 && old_wpid.face() != -1) { + old_charge = grouping->get_ave_3d_charge(old_pos, old_wpid.apa(), old_wpid.face(), 0.6*units::cm); + } + + if (new_wpid.apa() != -1 && new_wpid.face() != -1) { + new_charge = grouping->get_ave_3d_charge(new_pos, new_wpid.apa(), new_wpid.face(), 0.6*units::cm); + } + + // Check charge conditions - if new position has much lower charge, keep old position + if (new_charge < 5000 && new_charge < 0.4*old_charge) { + results.second = old_pos; + } else if (new_charge < 8000 && new_charge < 0.6*old_charge) { + // Reduce the strength - keep old position + results.second = old_pos; + new_charge = old_charge; + } + + // Update vertex and segment information with fitted position + if (results.first) + fcn.UpdateInfo(results.second, cluster, track_fitter, dv); + + return results.first; +} + + +void PatternAlgorithms::improve_vertex(Graph& graph, Facade::Cluster& cluster, VertexPtr main_vertex, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model, bool flag_search_vertex_activity, bool flag_final_vertex){ + + std::set fitted_vertices; + std::set existing_segments; + + // Check if all segments are showers, no need to fit vertex with only two legs + bool flag_skip_two_legs = false; + { + int ntracks = 0; + for (auto it = boost::edges(graph).first; it != boost::edges(graph).second; ++it) { + SegmentPtr sg = graph[*it].segment; + if (!sg || sg->cluster() != &cluster) continue; + existing_segments.insert(sg); + bool is_shower = sg->flags_any(SegmentFlags::kShowerTrajectory) || sg->flags_any(SegmentFlags::kShowerTopology); + if (!is_shower) ntracks++; + } + if (ntracks == 0) + flag_skip_two_legs = true; // all showers + } + + bool flag_found_vertex_activities = false; + + // Search for vertex activities + if (flag_search_vertex_activity) { + if (examine_structure_4(main_vertex, flag_final_vertex, graph, cluster, track_fitter, dv)) { + flag_found_vertex_activities = true; + track_fitter.do_multi_tracking(true, true, true); + } + } + + bool flag_update_fit = false; + + // Find and fit vertices + for (auto v : boost::make_iterator_range(boost::vertices(graph))) { + VertexPtr vtx = graph[v].vertex; + if (!vtx || vtx->cluster() != &cluster) continue; + + // Get segments connected to this vertex + std::set vertex_segments; + for (auto it = boost::out_edges(v, graph).first; it != boost::out_edges(v, graph).second; ++it) { + SegmentPtr sg = graph[*it].segment; + if (sg && sg->cluster() == &cluster) { + vertex_segments.insert(sg); + } + } + + if (vertex_segments.size() <= 2 && vtx != main_vertex) continue; + + int ntracks = 0, nshowers = 0; + int n_long_muons = 0; + for (auto sg : vertex_segments) { + bool is_shower = sg->flags_any(SegmentFlags::kShowerTrajectory) || sg->flags_any(SegmentFlags::kShowerTopology); + if (is_shower) nshowers++; + else ntracks++; + if (segments_in_long_muon.find(sg) != segments_in_long_muon.end()) n_long_muons++; + } + + if (ntracks == 0 && vtx != main_vertex) continue; + if (flag_skip_two_legs && vertex_segments.size() <= 2) continue; + + auto wcp_save = vtx->wcpt(); + + bool flag_update = fit_vertex(cluster, vtx, main_vertex, vertex_segments, track_fitter, dv); + if (flag_update) fitted_vertices.insert(vtx); + if (flag_update) { + flag_update_fit = true; + + double tmp_dis = std::sqrt(std::pow(wcp_save.point.x() - vtx->wcpt().point.x(), 2) + + std::pow(wcp_save.point.y() - vtx->wcpt().point.y(), 2) + + std::pow(wcp_save.point.z() - vtx->wcpt().point.z(), 2)); + + if (tmp_dis > 0.5*units::cm) { // if the vertex moved far, refit + track_fitter.do_multi_tracking(true, true, true); + fit_vertex(cluster, vtx, main_vertex, vertex_segments, track_fitter, dv); + } + } + + (void)nshowers; + (void)n_long_muons; + } + + if (flag_update_fit) { + // Do the overall fit again + track_fitter.do_multi_tracking(true, true, true); + + bool flag_keep_main_vertex = false; + Facade::geo_point_t main_vtx_pt; + if (main_vertex != nullptr) { + main_vtx_pt = main_vertex->fit().point; + flag_keep_main_vertex = true; + } + + examine_vertices(graph, cluster, track_fitter, dv); + + if (flag_keep_main_vertex) { + // Check if main_vertex still exists in graph + bool found_main_vertex = false; + for (auto v : boost::make_iterator_range(boost::vertices(graph))) { + if (graph[v].vertex == main_vertex) { + found_main_vertex = true; + break; + } + } + + if (!found_main_vertex) { + double min_dis = 1e9; + for (auto v : boost::make_iterator_range(boost::vertices(graph))) { + VertexPtr vtx = graph[v].vertex; + if (!vtx || vtx->cluster() != &cluster) continue; + double dis = std::sqrt(std::pow(vtx->fit().point.x() - main_vtx_pt.x(), 2) + + std::pow(vtx->fit().point.y() - main_vtx_pt.y(), 2) + + std::pow(vtx->fit().point.z() - main_vtx_pt.z(), 2)); + if (dis < min_dis) { + min_dis = dis; + main_vertex = vtx; + } + } + } + } + } + + std::vector refit_vertices; + flag_update_fit = false; + + if (flag_search_vertex_activity) { + // Search for vertex activities again + if (!flag_found_vertex_activities) { + if (examine_structure_4(main_vertex, flag_final_vertex, graph, cluster, track_fitter, dv)) { + flag_found_vertex_activities = true; + + // Get segments connected to main_vertex + std::set main_vertex_segments; + for (auto v : boost::make_iterator_range(boost::vertices(graph))) { + if (graph[v].vertex == main_vertex) { + for (auto it = boost::out_edges(v, graph).first; it != boost::out_edges(v, graph).second; ++it) { + SegmentPtr sg = graph[*it].segment; + if (sg && sg->cluster() == &cluster) { + main_vertex_segments.insert(sg); + } + } + break; + } + } + + if (main_vertex_segments.size() == 3) refit_vertices.push_back(main_vertex); + flag_update_fit = true; + } + } + + for (auto v : boost::make_iterator_range(boost::vertices(graph))) { + VertexPtr vtx = graph[v].vertex; + if (!vtx || vtx->cluster() != &cluster) continue; + + // Get segments connected to this vertex + std::set vertex_segments; + for (auto it = boost::out_edges(v, graph).first; it != boost::out_edges(v, graph).second; ++it) { + SegmentPtr sg = graph[*it].segment; + if (sg && sg->cluster() == &cluster) { + vertex_segments.insert(sg); + } + } + + if (vertex_segments.size() <= 2 && vtx != main_vertex) continue; + + int ntracks = 0, nshowers = 0; + for (auto sg : vertex_segments) { + bool is_shower = sg->flags_any(SegmentFlags::kShowerTrajectory) || sg->flags_any(SegmentFlags::kShowerTopology); + if (is_shower) nshowers++; + else ntracks++; + } + + if (ntracks == 0 && vtx != main_vertex) continue; + if (vertices_in_long_muon.find(vtx) != vertices_in_long_muon.end()) continue; + if (vtx == main_vertex && flag_found_vertex_activities) continue; + if (flag_skip_two_legs && vertex_segments.size() <= 2) continue; + + double search_range = 1.5*units::cm; + if (vertex_segments.size() == 1) search_range = 3.0*units::cm; + + bool flag_update = search_for_vertex_activities(graph, vtx, vertex_segments, cluster, track_fitter, dv, search_range); + + if (flag_update) { + // Get updated segments + vertex_segments.clear(); + for (auto it = boost::out_edges(v, graph).first; it != boost::out_edges(v, graph).second; ++it) { + SegmentPtr sg = graph[*it].segment; + if (sg && sg->cluster() == &cluster) { + vertex_segments.insert(sg); + } + } + if (vertex_segments.size() == 3) refit_vertices.push_back(vtx); + flag_update_fit = true; + } + (void)nshowers; + } + + if (flag_update_fit) { + // Do the overall fit again + track_fitter.do_multi_tracking(true, true, true); + flag_update_fit = false; + + // Redo the fit + for (auto vtx : refit_vertices) { + std::set vertex_segments; + for (auto v : boost::make_iterator_range(boost::vertices(graph))) { + if (graph[v].vertex == vtx) { + for (auto it = boost::out_edges(v, graph).first; it != boost::out_edges(v, graph).second; ++it) { + SegmentPtr sg = graph[*it].segment; + if (sg && sg->cluster() == &cluster) { + vertex_segments.insert(sg); + } + } + break; + } + } + + bool flag_update = fit_vertex(cluster, vtx, main_vertex, vertex_segments, track_fitter, dv); + if (flag_update) fitted_vertices.insert(vtx); + if (flag_update) flag_update_fit = true; + } + if (flag_update_fit) track_fitter.do_multi_tracking(true, true, true); + } + + // Eliminate short tracks + if (eliminate_short_vertex_activities(graph, cluster, main_vertex, existing_segments, track_fitter, dv)) { + track_fitter.do_multi_tracking(true, true, true); + } + + // Determine directions for segments + for (auto it = boost::edges(graph).first; it != boost::edges(graph).second; ++it) { + SegmentPtr sg1 = graph[*it].segment; + if (!sg1 || sg1->cluster() != &cluster) continue; + + if (!sg1->particle_info()) { + segment_is_shower_topology(sg1); + + VertexPtr start_v = nullptr, end_v = nullptr; + auto source_v = boost::source(*it, graph); + auto target_v = boost::target(*it, graph); + + auto& wcpts = sg1->wcpts(); + if (!wcpts.empty()) { + if ((graph[source_v].vertex->wcpt().point - wcpts.front().point).magnitude() < 0.01*units::cm) { + start_v = graph[source_v].vertex; + end_v = graph[target_v].vertex; + } else { + end_v = graph[source_v].vertex; + start_v = graph[target_v].vertex; + } + } + + if (start_v && end_v) { + int start_n = boost::out_degree(source_v, graph); + int end_n = boost::out_degree(target_v, graph); + + if (segment_is_shower_trajectory(sg1)) { + // Trajectory shower + segment_determine_shower_direction_trajectory(sg1, start_n, end_n, particle_data, recomb_model, 43000/units::cm, false); + } else { + segment_determine_dir_track(sg1, start_n, end_n, particle_data, recomb_model, 43000/units::cm, false); + } + } + } + } + } else { // flag_search_vertex_activity + for (auto vtx : fitted_vertices) { + // Find vertex descriptor + for (auto v : boost::make_iterator_range(boost::vertices(graph))) { + if (graph[v].vertex != vtx) continue; + + for (auto it = boost::out_edges(v, graph).first; it != boost::out_edges(v, graph).second; ++it) { + SegmentPtr sg = graph[*it].segment; + if (!sg || sg->cluster() != &cluster) continue; + + if (!sg->particle_info()) segment_is_shower_topology(sg); + + VertexPtr start_v = nullptr, end_v = nullptr; + auto source_v = boost::source(*it, graph); + auto target_v = boost::target(*it, graph); + + auto& wcpts = sg->wcpts(); + if (!wcpts.empty()) { + if ((graph[source_v].vertex->wcpt().point - wcpts.front().point).magnitude() < 0.01*units::cm) { + start_v = graph[source_v].vertex; + end_v = graph[target_v].vertex; + } else { + end_v = graph[source_v].vertex; + start_v = graph[target_v].vertex; + } + } + + bool flag_print = false; + bool is_shower = sg->flags_any(SegmentFlags::kShowerTrajectory) || sg->flags_any(SegmentFlags::kShowerTopology); + if (start_v && end_v && !is_shower) { + // Track + int start_n = boost::out_degree(source_v, graph); + int end_n = boost::out_degree(target_v, graph); + segment_determine_dir_track(sg, start_n, end_n, particle_data, recomb_model, 43000/units::cm, flag_print); + } + } + } + } + } + + // Handle special cases for main_vertex segments + if (main_vertex != nullptr && main_vertex->cluster() == &cluster) { + // Find main_vertex descriptor + for (auto v : boost::make_iterator_range(boost::vertices(graph))) { + if (graph[v].vertex != main_vertex) continue; + + for (auto it = boost::out_edges(v, graph).first; it != boost::out_edges(v, graph).second; ++it) { + SegmentPtr sg = graph[*it].segment; + if (!sg || sg->cluster() != &cluster) continue; + + std::pair pair_result = calculate_num_daughter_showers(graph, main_vertex, sg, false); + + double medium_dQdx = segment_median_dQ_dx(sg); + if ((pair_result.first <= 2 || (medium_dQdx/(43e3/units::cm) > 1.6 && pair_result.first <= 3)) && segment_is_shower_trajectory(sg)) { + if (!segment_is_shower_trajectory(sg, 1.0*units::cm, 43000/units::cm)) { + VertexPtr start_v = nullptr, end_v = nullptr; + auto source_v = boost::source(*it, graph); + auto target_v = boost::target(*it, graph); + + auto& wcpts = sg->wcpts(); + if (!wcpts.empty()) { + if ((graph[source_v].vertex->wcpt().point - wcpts.front().point).magnitude() < 0.01*units::cm) { + start_v = graph[source_v].vertex; + end_v = graph[target_v].vertex; + } else { + end_v = graph[source_v].vertex; + start_v = graph[target_v].vertex; + } + } + + if (start_v && end_v) { + int start_n = boost::out_degree(source_v, graph); + int end_n = boost::out_degree(target_v, graph); + segment_determine_dir_track(sg, start_n, end_n, particle_data, recomb_model, 43000/units::cm, false); + } + } + } + + // Examine topology case + if (pair_result.first == 1 && segment_is_shower_topology(sg, false)) { + int dir_save = sg->dirsign(); + + VertexPtr start_v = nullptr, end_v = nullptr; + auto source_v = boost::source(*it, graph); + auto target_v = boost::target(*it, graph); + + auto& wcpts = sg->wcpts(); + if (!wcpts.empty()) { + if ((graph[source_v].vertex->wcpt().point - wcpts.front().point).magnitude() < 0.01*units::cm) { + start_v = graph[source_v].vertex; + end_v = graph[target_v].vertex; + } else { + end_v = graph[source_v].vertex; + start_v = graph[target_v].vertex; + } + } + + if (start_v && end_v) { + sg->unset_flags(SegmentFlags::kShowerTopology); + int start_n = boost::out_degree(source_v, graph); + int end_n = boost::out_degree(target_v, graph); + segment_determine_dir_track(sg, start_n, end_n, particle_data, recomb_model, 43000/units::cm, false); + + if ((sg->particle_info() && sg->particle_info()->pdg() == 2212 && sg->particle_score() < 0.09) || + (sg->particle_info() && sg->particle_info()->pdg() == 13 && sg->particle_score() < 0.06)) { + sg->unset_flags(SegmentFlags::kShowerTopology); + } else { + sg->set_flags(SegmentFlags::kShowerTopology); + if (!sg->particle_info()) { + sg->particle_info() = std::make_shared(); + } + sg->particle_info()->set_pdg(11); + sg->particle_score(100); + sg->dirsign(dir_save); + sg->particle_info()->set_mass(particle_data->get_particle_mass(11)); + } + } + } + + if (flag_skip_two_legs && existing_segments.find(sg) == existing_segments.end()) { + VertexPtr start_v = nullptr, end_v = nullptr; + auto source_v = boost::source(*it, graph); + auto target_v = boost::target(*it, graph); + + auto& wcpts = sg->wcpts(); + if (!wcpts.empty()) { + if ((graph[source_v].vertex->wcpt().point - wcpts.front().point).magnitude() < 0.01*units::cm) { + start_v = graph[source_v].vertex; + end_v = graph[target_v].vertex; + } else { + end_v = graph[source_v].vertex; + start_v = graph[target_v].vertex; + } + } + + if (start_v && end_v) { + int start_n = boost::out_degree(source_v, graph); + int end_n = boost::out_degree(target_v, graph); + segment_determine_dir_track(sg, start_n, end_n, particle_data, recomb_model, 43000/units::cm, false); + + if ((!sg->particle_info() || sg->dir_weak()) && medium_dQdx/(43e3/units::cm) < 1.3) { + if (!sg->particle_info()) { + sg->particle_info() = std::make_shared(); + } + sg->particle_info()->set_pdg(11); + sg->particle_score(100); + sg->particle_info()->set_mass(particle_data->get_particle_mass(11)); + } + } + } + } + break; + } + } +} + +void PatternAlgorithms::determine_main_vertex(Graph& graph, Facade::Cluster& cluster, VertexPtr main_vertex, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model, bool flag_print){ + + // Find the main vertex - check if we have only showers + bool flag_save_only_showers = true; + for (auto v : boost::make_iterator_range(boost::vertices(graph))) { + VertexPtr vtx = graph[v].vertex; + if (!vtx || vtx->cluster() != &cluster) continue; + + auto results = examine_main_vertex_candidate(graph, vtx); + bool flag_in = std::get<0>(results); + int ntracks = std::get<1>(results); + // int nshowers = std::get<2>(results); + + if (!flag_in) { + if (ntracks > 0) { + flag_save_only_showers = false; + break; + } + } + } + + // Improve vertex if not only showers and cluster is main cluster + if (!flag_save_only_showers && cluster.get_flag(Facade::Flags::main_cluster)) { + improve_vertex(graph, cluster, main_vertex, vertices_in_long_muon, segments_in_long_muon, track_fitter, dv, particle_data, recomb_model, false); + // Fix maps with shower in and track out + fix_maps_shower_in_track_out(graph, cluster); + } + + // Build map of vertex candidates and their track/shower counts + std::map> map_vertex_track_shower; + std::vector main_vertex_candidates; + + for (auto v : boost::make_iterator_range(boost::vertices(graph))) { + VertexPtr vtx = graph[v].vertex; + if (!vtx || vtx->cluster() != &cluster) continue; + + auto results = examine_main_vertex_candidate(graph, vtx); + bool flag_in = std::get<0>(results); + int ntracks = std::get<1>(results); + int nshowers = std::get<2>(results); + + if (!flag_in) { + map_vertex_track_shower[vtx] = std::make_pair(ntracks, nshowers); + } + } + + // Select main vertex candidates based on topology + if (flag_save_only_showers) { + // For all showers case, add vertices with 1 segment first + for (auto v : boost::make_iterator_range(boost::vertices(graph))) { + VertexPtr vtx = graph[v].vertex; + if (!vtx || vtx->cluster() != &cluster) continue; + + int num_segs = 0; + if (vtx->descriptor_valid()) { + auto vd = vtx->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + if (graph[*e_it].segment) num_segs++; + } + } + + if (num_segs == 1) { + main_vertex_candidates.push_back(vtx); + } + } + + // Add remaining candidates + for (auto it = map_vertex_track_shower.begin(); it != map_vertex_track_shower.end(); it++) { + if (std::find(main_vertex_candidates.begin(), main_vertex_candidates.end(), it->first) == main_vertex_candidates.end()) { + main_vertex_candidates.push_back(it->first); + } + } + } else { + // For mixed case, only add vertices with tracks + for (auto it = map_vertex_track_shower.begin(); it != map_vertex_track_shower.end(); it++) { + if (it->second.first > 0) { + main_vertex_candidates.push_back(it->first); + } + } + } + + // Determine main vertex based on candidates + if (flag_save_only_showers) { + if (main_vertex_candidates.size() > 0) { + if (flag_print) { + std::cout << "Determining the main vertex with all showers: " << main_vertex_candidates.size() + << " in cluster " << cluster.get_cluster_id() << std::endl; + } + main_vertex = compare_main_vertices_all_showers(graph, cluster, main_vertex_candidates, track_fitter, dv, particle_data, recomb_model); + } else { + return; + } + } else { + // Examine main vertex candidates to filter and identify back-to-back tracks + examine_main_vertices_local(graph, main_vertex_candidates, particle_data, recomb_model); + + if (flag_print) { + for (auto vtx : main_vertex_candidates) { + std::cout << "Candidate main vertex " << vtx->fit().point << " connecting to: "; + + if (vtx->descriptor_valid()) { + auto vd = vtx->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr sg = graph[*e_it].segment; + if (sg) { + std::cout << sg->id() << ", "; + } + } + } + std::cout << " in cluster " << cluster.get_cluster_id() << std::endl; + } + } + + if (main_vertex_candidates.size() == 1) { + main_vertex = main_vertex_candidates.front(); + } else if (main_vertex_candidates.size() > 1) { + main_vertex = compare_main_vertices(graph, cluster, main_vertex_candidates); + } else { + return; + } + } + + // Examine structure for non-shower cases + if (!flag_save_only_showers) { + examine_structure_final(graph, main_vertex, cluster, track_fitter, dv); + } + + // Examine directions + bool flag_check = examine_direction(graph, main_vertex, main_vertex, vertices_in_long_muon, segments_in_long_muon, particle_data, recomb_model, false); + if (!flag_check) { + std::cout << "Wrong: inconsistency for track directions in cluster " << cluster.get_cluster_id() << std::endl; + } + + if (flag_print) { + std::cout << "Main Vertex " << main_vertex->fit().point << " connecting to: "; + + if (main_vertex->descriptor_valid()) { + auto vd = main_vertex->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr sg = graph[*e_it].segment; + if (sg) { + std::cout << sg->id() << ", "; + } + } + } + std::cout << std::endl; + + print_segs_info(graph, cluster, main_vertex); + } +} + +void PatternAlgorithms::change_daughter_type(Graph& graph, VertexPtr vertex, SegmentPtr segment, int particle_type, double mass, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model){ + // Find the other vertex of this segment + VertexPtr other_vtx = find_other_vertex(graph, segment, vertex); + if (!other_vtx) return; + + // Get vertex point + WireCell::Point other_vtx_pt = other_vtx->fit().valid() ? other_vtx->fit().point : other_vtx->wcpt().point; + + // Calculate direction from the other vertex + Facade::geo_vector_t dir1 = segment_cal_dir_3vector(segment, other_vtx_pt, 15*units::cm); + + // Check if other vertex has valid descriptor + if (!other_vtx->descriptor_valid()) return; + + // Iterate through all segments connected to the other vertex + auto other_vd = other_vtx->get_descriptor(); + auto edge_range = boost::out_edges(other_vd, graph); + + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr sg1 = graph[*e_it].segment; + if (!sg1 || sg1 == segment) continue; + + // Skip if already the same particle type + int current_pdg = sg1->has_particle_info() ? sg1->particle_info()->pdg() : 0; + if (current_pdg == particle_type) continue; + + // Skip if shower trajectory or has strong direction + if (sg1->flags_any(SegmentFlags::kShowerTrajectory)) continue; + if (!sg1->dir_weak() && sg1->dirsign() != 0) continue; + + // Check shower topology case: long segments with no direction + if (sg1->flags_any(SegmentFlags::kShowerTopology) && + sg1->dirsign() == 0 && + segment_track_length(sg1) > 40*units::cm) { + + // Calculate direction at 40cm + Facade::geo_vector_t dir2 = segment_cal_dir_3vector(sg1, other_vtx_pt, 40*units::cm); + + if (dir1.magnitude() > 0 && dir2.magnitude() > 0) { + double cos_angle = std::clamp(dir1.dot(dir2) / (dir1.magnitude() * dir2.magnitude()), -1.0, 1.0); + double angle = std::acos(cos_angle) / M_PI * 180.0; + + if (angle > 170) { + // Change particle type and mass + if (!sg1->particle_info()) { + sg1->particle_info() = std::make_shared(); + } + sg1->particle_info()->set_pdg(particle_type); + sg1->particle_info()->set_mass(mass); + sg1->unset_flags(SegmentFlags::kShowerTopology); + + // Recursively propagate changes + change_daughter_type(graph, other_vtx, sg1, particle_type, mass, particle_data, recomb_model); + VertexPtr sg1_other_vtx = find_other_vertex(graph, sg1, other_vtx); + if (sg1_other_vtx) { + change_daughter_type(graph, sg1_other_vtx, sg1, particle_type, mass, particle_data, recomb_model); + } + } + } + continue; + } else if (sg1->flags_any(SegmentFlags::kShowerTopology) && + sg1->dirsign() == 0 && + segment_track_length(sg1) <= 40*units::cm) { + continue; + } + + // Check general case: segments longer than 10cm + if (segment_track_length(sg1) > 10*units::cm) { + // Calculate direction at 15cm + Facade::geo_vector_t dir2 = segment_cal_dir_3vector(sg1, other_vtx_pt, 15*units::cm); + + if (dir1.magnitude() > 0 && dir2.magnitude() > 0) { + double cos_angle = std::clamp(dir1.dot(dir2) / (dir1.magnitude() * dir2.magnitude()), -1.0, 1.0); + double angle = std::acos(cos_angle) / M_PI * 180.0; + + if (angle > 165) { + // Change particle type and mass + if (!sg1->particle_info()) { + sg1->particle_info() = std::make_shared(); + } + sg1->particle_info()->set_pdg(particle_type); + sg1->particle_info()->set_mass(mass); + + // Recursively propagate changes + change_daughter_type(graph, other_vtx, sg1, particle_type, mass, particle_data, recomb_model); + VertexPtr sg1_other_vtx = find_other_vertex(graph, sg1, other_vtx); + if (sg1_other_vtx) { + change_daughter_type(graph, sg1_other_vtx, sg1, particle_type, mass, particle_data, recomb_model); + } + } + } + } + } +} + +void PatternAlgorithms::examine_main_vertices_local(Graph& graph, std::vector& vertices, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model){ + if (vertices.size() == 1) return; + + double max_length = 0; + std::set tmp_vertices; + + for (auto vtx : vertices) { + if (!vtx || !vtx->descriptor_valid()) continue; + + // Count segments connected to this vertex + int num_segs = 0; + auto vd = vtx->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + if (graph[*e_it].segment) num_segs++; + } + + // If only 1 segment, add to tmp_vertices + if (num_segs == 1) { + tmp_vertices.insert(vtx); + } else { + // Check pairs of segments for back-to-back tracks + std::set used_segments; + WireCell::Point vtx_pt = vtx->fit().valid() ? vtx->fit().point : vtx->wcpt().point; + + // Collect all segments and check pairs + std::vector vertex_segments; + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr sg = graph[*e_it].segment; + if (sg) vertex_segments.push_back(sg); + } + + for (size_t i = 0; i < vertex_segments.size(); i++) { + SegmentPtr sg1 = vertex_segments[i]; + double length1 = segment_track_length(sg1); + if (length1 < 10*units::cm) continue; + + Facade::geo_vector_t dir1 = segment_cal_dir_3vector(sg1, vtx_pt, 15*units::cm); + Facade::geo_vector_t dir3 = segment_cal_dir_3vector(sg1, vtx_pt, 30*units::cm); + + if (length1 > max_length) max_length = length1; + + for (size_t j = i + 1; j < vertex_segments.size(); j++) { + SegmentPtr sg2 = vertex_segments[j]; + double length2 = segment_track_length(sg2); + if (length2 < 10*units::cm) continue; + + Facade::geo_vector_t dir2 = segment_cal_dir_3vector(sg2, vtx_pt, 15*units::cm); + Facade::geo_vector_t dir4 = segment_cal_dir_3vector(sg2, vtx_pt, 30*units::cm); + + double angle1 = 0, angle2 = 0; + if (dir1.magnitude() > 0 && dir2.magnitude() > 0) { + double cos_angle = std::clamp(dir1.dot(dir2) / (dir1.magnitude() * dir2.magnitude()), -1.0, 1.0); + angle1 = std::acos(cos_angle) / M_PI * 180.0; + } + if (dir3.magnitude() > 0 && dir4.magnitude() > 0) { + double cos_angle = std::clamp(dir3.dot(dir4) / (dir3.magnitude() * dir4.magnitude()), -1.0, 1.0); + angle2 = std::acos(cos_angle) / M_PI * 180.0; + } + + int pdg1 = sg1->has_particle_info() ? sg1->particle_info()->pdg() : 0; + int pdg2 = sg2->has_particle_info() ? sg2->particle_info()->pdg() : 0; + + // Check for back-to-back muon tracks + if ((angle1 > 165 || angle2 > 165) && + (pdg1 == 13 || pdg2 == 13) && + (length1 > 30*units::cm || length2 > 30*units::cm)) { + used_segments.insert(sg1); + used_segments.insert(sg2); + } + // Check for back-to-back proton tracks + else if ((angle1 > 170 || angle2 > 170) && + ((pdg1 == 2212 && (pdg2 == 0 || pdg2 == 2212)) || + (pdg2 == 2212 && (pdg1 == 0 || pdg1 == 2212))) && + (length1 > 20*units::cm && length2 > 20*units::cm)) { + used_segments.insert(sg1); + used_segments.insert(sg2); + } + } + } + + // If we found back-to-back tracks, check remaining segments + if (used_segments.size() > 0) { + bool flag_skip = true; + + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr sg1 = graph[*e_it].segment; + if (!sg1) continue; + if (used_segments.find(sg1) != used_segments.end()) continue; + + double length = segment_track_length(sg1); + bool is_shower = sg1->flags_any(SegmentFlags::kShowerTrajectory) || + sg1->flags_any(SegmentFlags::kShowerTopology); + + if (is_shower) { + // Check shower significance + auto pair_result = calculate_num_daughter_showers(graph, vtx, sg1, false); + if (pair_result.second > 35*units::cm) { + flag_skip = false; + break; + } + } else { + // Check track significance + if (!sg1->dir_weak() && length > 6*units::cm) { + flag_skip = false; + break; + } + } + } + + if (!flag_skip) { + tmp_vertices.insert(vtx); + } else { + // Change particle types to muons for back-to-back tracks + double muon_mass = particle_data->get_particle_mass(13); + + for (auto sg1 : used_segments) { + // Skip shower trajectory + if (sg1->flags_any(SegmentFlags::kShowerTrajectory)) continue; + + // Handle shower topology + if (sg1->flags_any(SegmentFlags::kShowerTopology)) { + if (segment_track_length(sg1) > 40*units::cm && sg1->dirsign() == 0) { + if (!sg1->particle_info()) { + sg1->particle_info() = std::make_shared(); + } + sg1->particle_info()->set_pdg(13); + sg1->particle_info()->set_mass(muon_mass); + sg1->unset_flags(SegmentFlags::kShowerTopology); + + change_daughter_type(graph, vtx, sg1, 13, muon_mass, particle_data, recomb_model); + VertexPtr other_vtx = find_other_vertex(graph, sg1, vtx); + if (other_vtx) { + change_daughter_type(graph, other_vtx, sg1, 13, muon_mass, particle_data, recomb_model); + } + } else { + continue; + } + } + // Handle non-muon particles + else { + int current_pdg = sg1->has_particle_info() ? sg1->particle_info()->pdg() : 0; + if (current_pdg != 13) { + if (!sg1->particle_info()) { + sg1->particle_info() = std::make_shared(); + } + sg1->particle_info()->set_pdg(13); + sg1->particle_info()->set_mass(muon_mass); + + change_daughter_type(graph, vtx, sg1, 13, muon_mass, particle_data, recomb_model); + VertexPtr other_vtx = find_other_vertex(graph, sg1, vtx); + if (other_vtx) { + change_daughter_type(graph, other_vtx, sg1, 13, muon_mass, particle_data, recomb_model); + } + } + } + + // Find continuation muon segments and add final vertex + std::vector acc_vertices; + auto results = find_cont_muon_segment(graph, sg1, vtx); + while (results.first != nullptr) { + acc_vertices.push_back(results.second); + results = find_cont_muon_segment(graph, results.first, results.second); + } + + if (acc_vertices.size() > 0 && acc_vertices.back() != nullptr) { + tmp_vertices.insert(acc_vertices.back()); + } + } + } + } else { + // No back-to-back tracks found, keep this vertex + tmp_vertices.insert(vtx); + } + } + } + + // Update vertices collection + if (tmp_vertices.size() == 0) return; + + vertices.clear(); + vertices.resize(tmp_vertices.size()); + std::copy(tmp_vertices.begin(), tmp_vertices.end(), vertices.begin()); +} + + +VertexPtr PatternAlgorithms::compare_main_vertices_global(Graph& graph, std::vector& vertex_candidates, Facade::Cluster& main_cluster, TrackFitting& track_fitter, IDetectorVolumes::pointer dv){ + if (vertex_candidates.empty()) return nullptr; + + bool flag_print = false; + + // Initialize scoring map + std::map map_vertex_num; + for (auto vtx : vertex_candidates) { + map_vertex_num[vtx] = 0; + } + + // Score based on z position (prefer earlier/upstream vertices) + double min_z = 1e9; + for (auto vtx : vertex_candidates) { + WireCell::Point vtx_pt = vtx->fit().valid() ? vtx->fit().point : vtx->wcpt().point; + if (vtx_pt.z() < min_z) min_z = vtx_pt.z(); + } + + for (auto vtx : vertex_candidates) { + WireCell::Point vtx_pt = vtx->fit().valid() ? vtx->fit().point : vtx->wcpt().point; + map_vertex_num[vtx] -= (vtx_pt.z() - min_z) / (200 * units::cm); + + // Score based on segments connected to this vertex + if (vtx->descriptor_valid()) { + auto vd = vtx->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr sg = graph[*e_it].segment; + if (!sg) continue; + + bool is_shower = sg->flags_any(SegmentFlags::kShowerTrajectory) || + sg->flags_any(SegmentFlags::kShowerTopology); + + if (is_shower) { + map_vertex_num[vtx] += 1.0 / 4.0 / 2.0; // showers count less + } else { + map_vertex_num[vtx] += 1.0 / 4.0; // tracks + } + + // Bonus for clear protons or tracks with direction + int pdg = sg->has_particle_info() ? sg->particle_info()->pdg() : 0; + int dirsign = sg->dirsign(); + bool is_dir_weak = sg->dir_weak(); + + if (pdg == 2212 && dirsign != 0 && !is_dir_weak) { + map_vertex_num[vtx] += 1.0 / 4.0; // clear proton + } else if (dirsign != 0 && !is_shower) { + map_vertex_num[vtx] += 1.0 / 4.0 / 2.0; // track with direction + } + } + } + + // Bonus if vertex is in main cluster + if (vtx->cluster() == &main_cluster) { + map_vertex_num[vtx] += 0.25; + } + + if (flag_print) { + std::cout << "A: " << map_vertex_num[vtx] << " " << (vtx_pt.z() - min_z) / (200 * units::cm) << std::endl; + } + } + + // Score based on fiducial volume + auto grouping = main_cluster.grouping(); + auto fiducial_utils = grouping ? grouping->get_fiducialutils() : nullptr; + + for (auto vtx : vertex_candidates) { + WireCell::Point vtx_pt = vtx->fit().valid() ? vtx->fit().point : vtx->wcpt().point; + + bool in_fv = fiducial_utils && fiducial_utils->inside_fiducial_volume(vtx_pt); + if (in_fv || vtx->cluster() == &main_cluster) { + map_vertex_num[vtx] += 0.5; + } + + if (flag_print) { + std::cout << "B: " << map_vertex_num[vtx] << " " << in_fv << std::endl; + } + } + + // Calculate direction for each vertex + std::map map_vertex_dir; + for (auto vtx : vertex_candidates) { + map_vertex_dir[vtx] = vertex_get_dir(vtx, graph, 5 * units::cm); + } + + // Score based on whether other vertices point toward this vertex + for (auto vtx : vertex_candidates) { + WireCell::Point vtx_pt = vtx->fit().valid() ? vtx->fit().point : vtx->wcpt().point; + double delta = 0; + + for (auto vtx1 : vertex_candidates) { + if (vtx1 == vtx) continue; + + WireCell::Point vtx1_pt = vtx1->fit().valid() ? vtx1->fit().point : vtx1->wcpt().point; + + // Direction from vtx to vtx1 + Facade::geo_vector_t dir(vtx1_pt.x() - vtx_pt.x(), + vtx1_pt.y() - vtx_pt.y(), + vtx1_pt.z() - vtx_pt.z()); + + Facade::geo_vector_t dir1 = map_vertex_dir[vtx1]; + + if (dir.magnitude() > 0 && dir1.magnitude() > 0) { + double cos_angle = std::clamp(dir.dot(dir1) / (dir.magnitude() * dir1.magnitude()), -1.0, 1.0); + double angle = std::acos(cos_angle) / M_PI * 180.0; + + if (angle < 15) { + map_vertex_num[vtx] += 0.25; + delta++; + } else if (angle < 30) { + map_vertex_num[vtx] += 0.25 / 2.0; + delta++; + } + } + } + + // Penalize isolated vertices not in main cluster + if (delta == 0) { + double total_length = 0; + int num_tracks = 0; + + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + SegmentPtr seg = graph[*eit].segment; + if (!seg || seg->cluster() != vtx->cluster()) continue; + + total_length += segment_track_length(seg); + num_tracks++; + } + + if (vtx->cluster() != &main_cluster && total_length < 6 * units::cm) { + map_vertex_num[vtx] -= 0.25 * num_tracks; + } + } + + if (flag_print) { + std::cout << "E: " << map_vertex_num[vtx] << std::endl; + } + } + + // Find vertex with maximum score + double max_val = -1e9; + VertexPtr max_vertex = nullptr; + + for (auto vtx : vertex_candidates) { + if (map_vertex_num[vtx] > max_val) { + max_val = map_vertex_num[vtx]; + max_vertex = vtx; + } + } + + return max_vertex; +} + +Facade::Cluster* PatternAlgorithms::check_switch_main_cluster(Graph& graph, std::map map_cluster_main_vertices, Facade::Cluster* main_cluster, std::vector& other_clusters, TrackFitting& track_fitter, IDetectorVolumes::pointer dv){ + if (!main_cluster) return main_cluster; + + bool flag_all_showers = false; + bool flag_print = true; + + VertexPtr temp_main_vertex = nullptr; + + // Check if main cluster has a main vertex + if (map_cluster_main_vertices.find(main_cluster) != map_cluster_main_vertices.end()) { + temp_main_vertex = map_cluster_main_vertices[main_cluster]; + int n_showers = 0; + int n_total = 0; + + // Count showers connected to this vertex + if (temp_main_vertex && temp_main_vertex->descriptor_valid()) { + auto vd = temp_main_vertex->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr seg = graph[*e_it].segment; + if (!seg) continue; + + n_total++; + bool is_shower = seg->flags_any(SegmentFlags::kShowerTrajectory) || + seg->flags_any(SegmentFlags::kShowerTopology); + if (is_shower) n_showers++; + } + } + + if (n_total > 0 && n_showers == n_total) { + flag_all_showers = true; + } + } else { + flag_all_showers = true; + } + + // If all showers, consider switching main cluster + if (flag_all_showers) { + // Collect all vertex candidates + std::vector vertex_candidates; + for (auto& [cluster, vertex] : map_cluster_main_vertices) { + if (vertex) { + vertex_candidates.push_back(vertex); + } + } + + if (flag_print) { + for (auto vtx : vertex_candidates) { + WireCell::Point vtx_pt = vtx->fit().valid() ? vtx->fit().point : vtx->wcpt().point; + int cluster_id = vtx->cluster() ? vtx->cluster()->get_cluster_id() : -1; + std::cout << "Candidate main vertex " << cluster_id << " " << vtx_pt << " connecting to: "; + + if (vtx->descriptor_valid()) { + auto vd = vtx->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr seg = graph[*e_it].segment; + if (seg) { + std::cout << seg->id() << ", "; + } + } + } + std::cout << std::endl; + } + } + + // Compare all vertex candidates to find the best one + VertexPtr temp_main_vertex_1 = nullptr; + if (!vertex_candidates.empty()) { + temp_main_vertex_1 = compare_main_vertices_global(graph, vertex_candidates, *main_cluster, track_fitter, dv); + } + + // Check if we should switch + if (temp_main_vertex_1 && temp_main_vertex_1 != temp_main_vertex) { + if (temp_main_vertex) { + int old_id = temp_main_vertex->cluster() ? temp_main_vertex->cluster()->get_cluster_id() : -1; + int new_id = temp_main_vertex_1->cluster() ? temp_main_vertex_1->cluster()->get_cluster_id() : -1; + std::cout << "Switch Main Cluster " << old_id << " to " << new_id << std::endl; + } else { + int new_id = temp_main_vertex_1->cluster() ? temp_main_vertex_1->cluster()->get_cluster_id() : -1; + std::cout << "Switch Main Cluster to " << new_id << std::endl; + } + + // Find which cluster this vertex belongs to and swap + for (auto& [cluster, vertex] : map_cluster_main_vertices) { + if (vertex == temp_main_vertex_1 && cluster != main_cluster) { + main_cluster = swap_main_cluster(*cluster, *main_cluster, other_clusters); + break; + } + } + } + } + + return main_cluster; +} + +Facade::Cluster* PatternAlgorithms::check_switch_main_cluster_2(Graph& graph, VertexPtr temp_main_vertex, Facade::Cluster* max_length_cluster, Facade::Cluster* main_cluster, std::vector& other_clusters){ + if (!temp_main_vertex || !max_length_cluster || !main_cluster) return main_cluster; + + bool flag_switch = false; + + // Count showers connected to this vertex + int n_showers = 0; + int n_total = 0; + + if (temp_main_vertex->descriptor_valid()) { + auto vd = temp_main_vertex->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr seg = graph[*e_it].segment; + if (!seg) continue; + + n_total++; + bool is_shower = seg->flags_any(SegmentFlags::kShowerTrajectory) || + seg->flags_any(SegmentFlags::kShowerTopology); + if (is_shower) n_showers++; + } + } + + // If all segments are showers, consider switching + if (n_total > 0 && n_showers == n_total) { + flag_switch = true; + } + + if (flag_switch) { + std::cout << "Switch Main Cluster " << main_cluster->get_cluster_id() + << " to " << max_length_cluster->get_cluster_id() << std::endl; + main_cluster = swap_main_cluster(*max_length_cluster, *main_cluster, other_clusters); + } + + return main_cluster; +} + +VertexPtr PatternAlgorithms::determine_overall_main_vertex(Graph& graph, std::map map_cluster_main_vertices, Facade::Cluster* main_cluster, std::vector& other_clusters, std::set& vertices_in_long_muon, std::set& segments_in_long_muon, TrackFitting& track_fitter, IDetectorVolumes::pointer dv, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model ){ + if (!main_cluster) return nullptr; + + // Find cluster with maximum length + Facade::Cluster* max_length_cluster = nullptr; + double max_length = 0; + + // Check all clusters in the map + for (auto& [cluster, vertex] : map_cluster_main_vertices) { + if (!cluster) continue; + double length = cluster->get_length(); + if (length > max_length) { + max_length = length; + max_length_cluster = cluster; + } + } + + // Also check main cluster if not in map + if (map_cluster_main_vertices.find(main_cluster) == map_cluster_main_vertices.end()) { + double main_length = main_cluster->get_length(); + if (main_length > max_length) { + max_length = main_length; + max_length_cluster = main_cluster; + } + } + + // Check all other clusters + for (auto cluster : other_clusters) { + if (!cluster) continue; + double length = cluster->get_length(); + if (length > max_length) { + max_length = length; + max_length_cluster = cluster; + } + } + + // Examine main vertices first + examine_main_vertices(graph, map_cluster_main_vertices, main_cluster, other_clusters); + + // Check for main cluster switch + // For now, using simplified version (similar to frozen chain in original) + if (max_length_cluster && main_cluster) { + double main_length = main_cluster->get_length(); + if (max_length > main_length * 0.8) { + VertexPtr temp_main_vertex = map_cluster_main_vertices.find(main_cluster) != map_cluster_main_vertices.end() + ? map_cluster_main_vertices[main_cluster] : nullptr; + if (temp_main_vertex) { + main_cluster = check_switch_main_cluster_2(graph, temp_main_vertex, max_length_cluster, main_cluster, + other_clusters); + } + } + } + + // Get the main vertex + VertexPtr main_vertex = nullptr; + if (map_cluster_main_vertices.find(main_cluster) != map_cluster_main_vertices.end()) { + main_vertex = map_cluster_main_vertices[main_cluster]; + } + + if (!main_vertex) return nullptr; + + // Examine tracks connected to main vertex - look for short high dQ/dx proton candidates + if (main_vertex->descriptor_valid()) { + auto vd = main_vertex->get_descriptor(); + auto edge_range = boost::out_edges(vd, graph); + + for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { + SegmentPtr sg = graph[*e_it].segment; + if (!sg) continue; + + auto pair_results = calculate_num_daughter_showers(graph, main_vertex, sg, false); + double length = segment_track_length(sg); + double median_dqdx = segment_median_dQ_dx(sg) / (43e3 / units::cm); + + // Short segment with only 1 daughter and high dQ/dx -> likely proton + if (pair_results.first == 1 && length < 1.5 * units::cm && median_dqdx > 1.6) { + if (!sg->particle_info()) { + sg->particle_info() = std::make_shared(); + } + sg->particle_info()->set_pdg(2212); // proton + sg->particle_info()->set_mass(particle_data->get_particle_mass(2212)); + + // Calculate 4-momentum + auto four_momentum = segment_cal_4mom(sg, 2212, particle_data, recomb_model); + auto pinfo = std::make_shared(2212, particle_data->get_particle_mass(2212), particle_data->pdg_to_name(2212), four_momentum); + sg->particle_info(pinfo); + } + } + } + + // Clean up long muons - remove segments/vertices not in main cluster + { + std::set tmp_segments; + std::set tmp_vertices; + + // Find segments not in main cluster + for (auto seg : segments_in_long_muon) { + if (seg && seg->cluster() != main_cluster) { + tmp_segments.insert(seg); + } + } + + // Find vertices not in main cluster + for (auto vtx : vertices_in_long_muon) { + if (vtx && vtx->cluster() != main_cluster) { + tmp_vertices.insert(vtx); + } + } + + // Remove them from the long muon sets + for (auto seg : tmp_segments) { + segments_in_long_muon.erase(seg); + } + + for (auto vtx : tmp_vertices) { + vertices_in_long_muon.erase(vtx); + } + } + + return main_vertex; +} \ No newline at end of file diff --git a/clus/src/PRGraph.cxx b/clus/src/PRGraph.cxx index 0d858f7bc..2d5e364db 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 016d809ab..cdd6526dc 100644 --- a/clus/src/PRSegment.cxx +++ b/clus/src/PRSegment.cxx @@ -34,33 +34,86 @@ 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()); + 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); + // } - 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) { + create_segment_fit_point_cloud(shared_from_this(), dv, cloud_name); } - // Create dynamic point cloud with the fit points + } + + bool Segment::reset_global_indices(){ + if (m_pc_global_indices.empty()) { + return false; + } + m_pc_global_indices.clear(); + return true; + } + + bool Segment::reset_global_indices(const std::string& cloud_name){ + auto it = m_pc_global_indices.find(cloud_name); + if (it == m_pc_global_indices.end()) { + return false; + } + m_pc_global_indices.erase(it); + return true; + } + + void Segment::clear_fit(const IDetectorVolumes::pointer& dv, const std::string& cloud_name){ + // Unset the kFit flag + unset_flags(SegmentFlags::kFit); + + // Reset fit vector to match wcpts size and copy point data + m_fits.clear(); + m_fits.resize(m_wcpts.size()); + for (size_t i = 0; i != m_wcpts.size(); i++) { + m_fits.at(i).point = m_wcpts.at(i).point; + // Reset other Fit fields to default values + m_fits.at(i).dQ = -1; + m_fits.at(i).dx = 0; + m_fits.at(i).pu = -1; + m_fits.at(i).pv = -1; + m_fits.at(i).pw = -1; + m_fits.at(i).pt = 0; + m_fits.at(i).reduced_chi2 = -1; + m_fits.at(i).index = -1; + m_fits.at(i).range = -1; + m_fits.at(i).flag_fix = false; + } + + // Reset direction and particle information + m_dirsign = 0; + m_dir_weak = false; + m_particle_score = 100; + m_particle_info = nullptr; + + // Recreate the dynamic point cloud for fit points if (dv) { create_segment_fit_point_cloud(shared_from_this(), dv, cloud_name); } + reset_global_indices(); } + diff --git a/clus/src/PRSegmentFunctions.cxx b/clus/src/PRSegmentFunctions.cxx index 0621c5024..b28aaeea3 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,155 @@ 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); + } + + // 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 with 4-momentum for seg1 + int pdg = seg->particle_info()->pdg(); + auto four_momentum1 = segment_cal_4mom(seg1, pdg, particle_data, recomb_model); + auto pinfo1 = std::make_shared( + pdg, + particle_data->get_particle_mass(pdg), + particle_data->pdg_to_name(pdg), + four_momentum1 + ); + seg1->particle_info(pinfo1); + + // Copy particle info with 4-momentum for seg2 + auto four_momentum2 = segment_cal_4mom(seg2, pdg, particle_data, recomb_model); + auto pinfo2 = std::make_shared( + pdg, + particle_data->get_particle_mass(pdg), + particle_data->pdg_to_name(pdg), + four_momentum2 + ); + seg2->particle_info(pinfo2); + } - //.... more for segment - // dir_weak - // flags (dir, shower traj, shower topo) - // particle type and mass and score - // points clouds + // Copy dynamic point clouds if they exist + // The dpcloud() method returns the DynamicPointCloud associated with a given name + if (seg->dpcloud("fit")) { + create_segment_fit_point_cloud(seg1, dv, "fit"); + create_segment_fit_point_cloud(seg2, dv, "fit"); + } + + if (seg->dpcloud("main")) { + // Convert WCPoint to Point for create_segment_point_cloud + const auto& wcpts1 = seg1->wcpts(); + const auto& wcpts2 = seg2->wcpts(); + std::vector points1, points2; + points1.reserve(wcpts1.size()); + points2.reserve(wcpts2.size()); + for (const auto& wcp : wcpts1) { + points1.push_back(wcp.point); + } + for (const auto& wcp : wcpts2) { + points2.push_back(wcp.point); + } + create_segment_point_cloud(seg1, points1, dv, "main"); + create_segment_point_cloud(seg2, points2, dv, "main"); + } + + if (seg->dpcloud("associate_points")) { + // Redistribute associated points based on closest distance to seg1 vs seg2 + // This matches WCPPID lines 123-140 + + auto orig_dpc = seg->dpcloud("associate_points"); + const auto& orig_points = orig_dpc->get_points(); + + // Separate points based on which segment they're closer to + std::vector points1, points2; + points1.reserve(orig_points.size() / 2); // estimate + points2.reserve(orig_points.size() / 2); + + // Determine which cloud to use for distance calculations + // Prefer "fit" points, but fall back to "main" if "fit" is not available + std::string ref_cloud_name = "fit"; + if (!seg1->dpcloud("fit") || !seg2->dpcloud("fit")) { + ref_cloud_name = "main"; + // Ensure main clouds exist for both segments + if (!seg1->dpcloud("main") || !seg2->dpcloud("main")) { + raise("break_segment: cannot redistribute associate_points - neither 'fit' nor 'main' clouds available"); + } + } + + // Iterate through all associated points + for (const auto& dpc_point : orig_points) { + WireCell::Point point(dpc_point.x, dpc_point.y, dpc_point.z); + + // Compute closest distance to seg1 and seg2 using reference cloud + auto [dist1, _1] = segment_get_closest_point(seg1, point, ref_cloud_name); + auto [dist2, _2] = segment_get_closest_point(seg2, point, ref_cloud_name); + + // Add point to closer segment + if (dist1 < dist2) { + points1.push_back(dpc_point); + } else { + points2.push_back(dpc_point); + } + } + + // Get wpid_params from the original cloud + auto& cluster = *seg->cluster(); + const auto& wpids = cluster.grouping()->wpids(); + std::map> wpid_params; + std::map> wpid_U_dir; + std::map> wpid_V_dir; + std::map> wpid_W_dir; + std::set apas; + Facade::compute_wireplane_params(wpids, dv, wpid_params, wpid_U_dir, wpid_V_dir, wpid_W_dir, apas); + + // Create new DynamicPointClouds for associated points if we have any + if (!points1.empty()) { + // Create and populate seg1's associate_points cloud + auto dpc1 = std::make_shared(wpid_params); + dpc1->add_points(points1); + seg1->dpcloud("associate_points", dpc1); + } + + if (!points2.empty()) { + // Create and populate seg2's associate_points cloud + auto dpc2 = std::make_shared(wpid_params); + dpc2->add_points(points2); + seg2->dpcloud("associate_points", dpc2); + } - return true; + // 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 +761,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 +796,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 +978,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 +1057,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 +1147,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 +1188,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 +1209,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 +1243,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 +1308,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 +1349,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 +1374,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 +1423,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 +1439,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 +1449,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 +1458,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 +1526,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 +1569,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 +1645,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 +1777,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 +1786,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 +1931,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 +2112,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 +2247,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/PRShower.cxx b/clus/src/PRShower.cxx index 9bf336dd6..71003d241 100644 --- a/clus/src/PRShower.cxx +++ b/clus/src/PRShower.cxx @@ -1,10 +1,36 @@ #include "WireCellClus/PRShower.h" #include "WireCellClus/PRGraph.h" +#include "WireCellClus/PRSegmentFunctions.h" +#include "WireCellClus/PRShowerFunctions.h" +#include "WireCellClus/DynamicPointCloud.h" namespace WireCell::Clus::PR { + + // Default initialization constructor following WCPPID WCShower logic Shower::Shower(Graph& graph) : TrajectoryView(graph) + , m_full_graph(graph) { + // Initialize all ShowerData members to defaults + data.particle_type = 0; + data.kenergy_range = 0; + data.kenergy_dQdx = 0; + data.kenergy_charge = 0; + data.kenergy_best = 0; + + data.start_point = Point(0, 0, 0); + data.end_point = Point(0, 0, 0); + data.init_dir = Vector(0, 0, 0); + + data.start_connection_type = 0; + + // Initialize start vertex and segment to nullptr + m_start_vertex = nullptr; + m_start_segment = nullptr; + + // Set shower flag (following WCPPID) + set_flags(ShowerFlags::kShower); + unset_flags(ShowerFlags::kKinematics); } Shower::~Shower() @@ -26,7 +52,7 @@ namespace WireCell::Clus::PR { // Chainable setters /// Chainable setter of start vertex - Shower& Shower::start_vertex(VertexPtr vtx) + Shower& Shower::set_start_vertex(VertexPtr vtx, int type) { if (! vtx->descriptor_valid()) { m_start_vertex = nullptr; @@ -34,12 +60,16 @@ namespace WireCell::Clus::PR { } this->add_vertex(vtx); m_start_vertex = vtx; + data.start_connection_type = type; + + return *this; } + /// Chainable setter of start segment - Shower& Shower::start_segment(SegmentPtr seg) + Shower& Shower::set_start_segment(SegmentPtr seg, bool flag_include_vertices, const std::string& cloud_name_fit, const std::string& cloud_name_associate) { if (! seg->descriptor_valid()) { m_start_segment = nullptr; @@ -47,9 +77,1061 @@ namespace WireCell::Clus::PR { } this->add_segment(seg); m_start_segment = seg; + + // If flag_include_vertices is true, add all vertices connected to this segment + if (flag_include_vertices) { + // Get the two vertices connected to this segment from the full graph + auto vertices = find_vertices(m_full_graph, seg); + + // Add each vertex to the view (skip the start_vertex) + if (vertices.first && vertices.first != m_start_vertex) { + this->add_vertex(vertices.first); + } + if (vertices.second && vertices.second != m_start_vertex) { + this->add_vertex(vertices.second); + } + } + + // Merge dynamic point clouds from segment to shower with the provided names + if (!cloud_name_fit.empty()) { + auto seg_dpc_fit = seg->dpcloud(cloud_name_fit); + if (seg_dpc_fit) { + auto shower_dpc_fit = this->dpcloud(cloud_name_fit); + if (!shower_dpc_fit) { + // Create new DPC if it doesn't exist in shower, copying wpid_params from segment's DPC + // We need the wpid_params to construct a new DPC, but it's private + // For now, just share the pointer - we'll need to modify this if independent DPCs are needed + this->dpcloud(cloud_name_fit, seg_dpc_fit); + } else { + // Add points from segment's DPC to existing shower's DPC + shower_dpc_fit->add_points(seg_dpc_fit->get_points()); + } + } + } + + if (!cloud_name_associate.empty()) { + auto seg_dpc_associate = seg->dpcloud(cloud_name_associate); + if (seg_dpc_associate) { + auto shower_dpc_associate = this->dpcloud(cloud_name_associate); + if (!shower_dpc_associate) { + // Create new DPC if it doesn't exist in shower + this->dpcloud(cloud_name_associate, seg_dpc_associate); + } else { + // Add points from segment's DPC to existing shower's DPC + shower_dpc_associate->add_points(seg_dpc_associate->get_points()); + } + } + } + return *this; } + void Shower::add_segment(SegmentPtr seg, bool flag_include_vertices, const std::string& cloud_name_fit, const std::string& cloud_name_associate) + { + if (! seg->descriptor_valid()) { + return; + } + this->add_segment(seg); + + // If flag_include_vertices is true, add all vertices connected to this segment + if (flag_include_vertices) { + // Get the two vertices connected to this segment from the full graph + auto vertices = find_vertices(m_full_graph, seg); + + // Add each vertex to the view (skip the start_vertex) + if (vertices.first ) { + this->add_vertex(vertices.first); + } + if (vertices.second ) { + this->add_vertex(vertices.second); + } + } + + // Merge dynamic point clouds from segment to shower with the provided names + if (!cloud_name_fit.empty()) { + auto seg_dpc_fit = seg->dpcloud(cloud_name_fit); + if (seg_dpc_fit) { + auto shower_dpc_fit = this->dpcloud(cloud_name_fit); + if (!shower_dpc_fit) { + // Create new DPC if it doesn't exist in shower, copying wpid_params from segment's DPC + // We need the wpid_params to construct a new DPC, but it's private + // For now, just share the pointer - we'll need to modify this if independent DPCs are needed + this->dpcloud(cloud_name_fit, seg_dpc_fit); + } else { + // Add points from segment's DPC to existing shower's DPC + shower_dpc_fit->add_points(seg_dpc_fit->get_points()); + } + } + } + + if (!cloud_name_associate.empty()) { + auto seg_dpc_associate = seg->dpcloud(cloud_name_associate); + if (seg_dpc_associate) { + auto shower_dpc_associate = this->dpcloud(cloud_name_associate); + if (!shower_dpc_associate) { + // Create new DPC if it doesn't exist in shower + this->dpcloud(cloud_name_associate, seg_dpc_associate); + } else { + // Add points from segment's DPC to existing shower's DPC + shower_dpc_associate->add_points(seg_dpc_associate->get_points()); + } + } + } + } + + void Shower::set_flag_kinematics(bool val){ + if (val) { + set_flags(ShowerFlags::kKinematics); + } else { + unset_flags(ShowerFlags::kKinematics); + } + } + + bool Shower::get_flag_kinematics(){ + return flags_any(ShowerFlags::kKinematics); + } + + bool Shower::get_flag_shower(){ + return flags_any(ShowerFlags::kShower); + } + + void Shower::add_shower(Shower& shower, const std::string& cloud_name_fit, const std::string& cloud_name_associate){ + // Iterate through all vertices in the input shower's view using the nodes() accessor + for (auto vdesc : shower.nodes()) { + VertexPtr vtx = m_full_graph[vdesc].vertex; + if (vtx && vtx->descriptor_valid()) { + this->add_vertex(vtx); + } + } + + // Iterate through all segments in the input shower's view using the edges() accessor + for (auto edesc : shower.edges()) { + SegmentPtr seg = m_full_graph[edesc].segment; + if (seg && seg->descriptor_valid()) { + this->add_segment(seg); + + // Merge point clouds from this segment + // Handle "fit" point cloud + if (!cloud_name_fit.empty()) { + auto seg_dpc_fit = seg->dpcloud(cloud_name_fit); + if (seg_dpc_fit) { + auto shower_dpc_fit = this->dpcloud(cloud_name_fit); + if (!shower_dpc_fit) { + this->dpcloud(cloud_name_fit, seg_dpc_fit); + } else { + shower_dpc_fit->add_points(seg_dpc_fit->get_points()); + } + } + } + + // Handle "associate_points" point cloud + if (!cloud_name_associate.empty()) { + auto seg_dpc_associate = seg->dpcloud(cloud_name_associate); + if (seg_dpc_associate) { + auto shower_dpc_associate = this->dpcloud(cloud_name_associate); + if (!shower_dpc_associate) { + this->dpcloud(cloud_name_associate, seg_dpc_associate); + } else { + shower_dpc_associate->add_points(seg_dpc_associate->get_points()); + } + } + } + } + } + } + + void Shower::complete_structure_with_start_segment(std::set& used_segments, const std::string& cloud_name_fit, const std::string& cloud_name_associate) { + if (!m_start_segment || !m_start_segment->descriptor_valid()) return; + + std::vector new_segments; + std::vector new_vertices; + + // Add start_segment to the view and mark as used + used_segments.insert(m_start_segment); + + + // Find vertices connected to start_segment (excluding start_vertex) + auto vertices = find_vertices(m_full_graph, m_start_segment); + if (vertices.first && vertices.first != m_start_vertex) { + this->add_vertex(vertices.first); + new_vertices.push_back(vertices.first); + } + if (vertices.second && vertices.second != m_start_vertex) { + this->add_vertex(vertices.second); + new_vertices.push_back(vertices.second); + } + + // Worklist algorithm: explore connected segments and vertices + while (!new_vertices.empty() || !new_segments.empty()) { + // Process new vertices - find all segments connected to them + if (!new_vertices.empty()) { + VertexPtr vtx = new_vertices.back(); + new_vertices.pop_back(); + + // Find all segments connected to this vertex + if (vtx->descriptor_valid()) { + auto vdesc = vtx->get_descriptor(); + for (auto edesc : boost::make_iterator_range(boost::out_edges(vdesc, m_full_graph))) { + SegmentPtr seg = m_full_graph[edesc].segment; + if (seg && seg->descriptor_valid() && used_segments.find(seg) == used_segments.end()) { + this->add_segment(seg); + new_segments.push_back(seg); + used_segments.insert(seg); + + // Merge point clouds from this segment + if (!cloud_name_fit.empty()) { + auto seg_dpc_fit = seg->dpcloud(cloud_name_fit); + if (seg_dpc_fit) { + auto shower_dpc_fit = this->dpcloud(cloud_name_fit); + if (!shower_dpc_fit) { + this->dpcloud(cloud_name_fit, seg_dpc_fit); + } else { + shower_dpc_fit->add_points(seg_dpc_fit->get_points()); + } + } + } + + if (!cloud_name_associate.empty()) { + auto seg_dpc_associate = seg->dpcloud(cloud_name_associate); + if (seg_dpc_associate) { + auto shower_dpc_associate = this->dpcloud(cloud_name_associate); + if (!shower_dpc_associate) { + this->dpcloud(cloud_name_associate, seg_dpc_associate); + } else { + shower_dpc_associate->add_points(seg_dpc_associate->get_points()); + } + } + } + } + } + } + } + + // Process new segments - find all vertices connected to them + if (!new_segments.empty()) { + SegmentPtr seg = new_segments.back(); + new_segments.pop_back(); + + // Find vertices connected to this segment (excluding start_vertex) + auto vertices = find_vertices(m_full_graph, seg); + if (vertices.first && vertices.first != m_start_vertex) { + if (!this->has_node(vertices.first->get_descriptor())) { + this->add_vertex(vertices.first); + new_vertices.push_back(vertices.first); + } + } + if (vertices.second && vertices.second != m_start_vertex) { + if (!this->has_node(vertices.second->get_descriptor())) { + this->add_vertex(vertices.second); + new_vertices.push_back(vertices.second); + } + } + } + } + } + + void Shower::fill_sets(std::set& used_vertices, std::set& used_segments, bool flag_exclude_start_segment){ + // Fill used_vertices with all vertices in this shower's view + for (auto vdesc : this->nodes()) { + VertexPtr vtx = m_full_graph[vdesc].vertex; + if (vtx) { + used_vertices.insert(vtx); + } + } + + // Fill used_segments with all segments in this shower's view + for (auto edesc : this->edges()) { + SegmentPtr seg = m_full_graph[edesc].segment; + if (seg) { + // Skip start_segment if flag is set + if (flag_exclude_start_segment && seg == m_start_segment) { + continue; + } + used_segments.insert(seg); + } + } + } + + void Shower::fill_point_vector(std::vector& points, bool flag_main){ + // Get the main cluster ID if flag_main is true + const Facade::Cluster* main_cluster = nullptr; + if (flag_main && m_start_segment && m_start_segment->cluster()) { + main_cluster = m_start_segment->cluster(); + } + + // Fill points from all segments in the shower's view + for (auto edesc : this->edges()) { + SegmentPtr seg = m_full_graph[edesc].segment; + if (seg) { + // Skip if flag_main is set and segment is not in the main cluster + if (flag_main && main_cluster && seg->cluster() != main_cluster) { + continue; + } + + // Get segment fit points and add all except first and last + const auto& fits = seg->fits(); + for (size_t i = 1; i + 1 < fits.size(); i++) { + points.push_back(fits[i].point); + } + } + } + + // Fill points from all vertices in the shower's view + for (auto vdesc : this->nodes()) { + VertexPtr vtx = m_full_graph[vdesc].vertex; + if (vtx) { + // Skip if flag_main is set and vertex is not in the main cluster + if (flag_main && main_cluster && vtx->cluster() != main_cluster) { + continue; + } + + // Add the vertex fit point + points.push_back(vtx->fit().point); + } + } + } + TrajectoryView& Shower::fill_maps() { + return *this; + } + + std::pair, std::set> Shower::get_connected_pieces(SegmentPtr seg){ + std::set used_segments; + std::set used_vertices; + + // Check if the segment is valid and in the view + if (!seg || !seg->descriptor_valid() || !this->has_edge(seg->get_descriptor())) { + return std::make_pair(used_vertices, used_segments); + } + + std::vector new_segments; + std::vector new_vertices; + + // Start with the given segment + new_segments.push_back(seg); + used_segments.insert(seg); + + // Worklist algorithm: explore connected segments and vertices in the view + while (!new_vertices.empty() || !new_segments.empty()) { + // Process new vertices - find all segments connected to them in the view + if (!new_vertices.empty()) { + VertexPtr vtx = new_vertices.back(); + new_vertices.pop_back(); + + // Find all segments connected to this vertex in the full graph, then check if in view + if (vtx->descriptor_valid()) { + auto vdesc = vtx->get_descriptor(); + for (auto edesc : boost::make_iterator_range(boost::out_edges(vdesc, m_full_graph))) { + // Check if this edge is in the view + if (this->has_edge(edesc)) { + SegmentPtr seg1 = m_full_graph[edesc].segment; + if (seg1 && used_segments.find(seg1) == used_segments.end()) { + new_segments.push_back(seg1); + used_segments.insert(seg1); + } + } + } + } + } + + // Process new segments - find all vertices connected to them in the view + if (!new_segments.empty()) { + SegmentPtr seg1 = new_segments.back(); + new_segments.pop_back(); + + // Find vertices connected to this segment in the full graph + auto vertices = find_vertices(m_full_graph, seg1); + + // Check if vertices are in the view and not yet visited + if (vertices.first && this->has_node(vertices.first->get_descriptor()) + && used_vertices.find(vertices.first) == used_vertices.end()) { + new_vertices.push_back(vertices.first); + used_vertices.insert(vertices.first); + } + if (vertices.second && this->has_node(vertices.second->get_descriptor()) + && used_vertices.find(vertices.second) == used_vertices.end()) { + new_vertices.push_back(vertices.second); + used_vertices.insert(vertices.second); + } + } + } + + return std::make_pair(used_vertices, used_segments); + } + + std::pair Shower::get_last_segment_vertex_long_muon(std::set& segments_in_muons) { + VertexPtr s_vtx = m_start_vertex; + SegmentPtr s_seg = m_start_segment; + + if (!s_vtx || !s_seg) { + return std::make_pair(s_seg, s_vtx); + } + + std::set used_segments; + used_segments.insert(s_seg); + + bool flag_continue = true; + while (flag_continue) { + flag_continue = false; + + // If current vertex is start_vertex, continue + if (s_vtx == m_start_vertex) { + flag_continue = true; + } else { + // Look for a new segment connected to s_vtx that is in segments_in_muons and not used + if (s_vtx->descriptor_valid()) { + auto vdesc = s_vtx->get_descriptor(); + // Iterate over segments connected to this vertex in the full graph, then check if in view + for (auto edesc : boost::make_iterator_range(boost::out_edges(vdesc, m_full_graph))) { + // Check if this edge is in the view + if (this->has_edge(edesc)) { + SegmentPtr sg = m_full_graph[edesc].segment; + if (sg && segments_in_muons.find(sg) != segments_in_muons.end() + && used_segments.find(sg) == used_segments.end()) { + s_seg = sg; + used_segments.insert(s_seg); + flag_continue = true; + break; + } + } + } + } + } + + // If we found a new segment, find the other vertex connected to it + if (flag_continue) { + auto vertices = find_vertices(m_full_graph, s_seg); + if (vertices.first && vertices.first != s_vtx) { + s_vtx = vertices.first; + } else if (vertices.second && vertices.second != s_vtx) { + s_vtx = vertices.second; + } + } + } + + return std::make_pair(s_seg, s_vtx); + } + + int Shower::get_num_main_segments() { + int num = 0; + + // If no start segment, return 0 + if (!m_start_segment) { + return 0; + } + + // Get the start segment's cluster + auto start_cluster = m_start_segment->cluster(); + if (!start_cluster) { + return 0; + } + + // Get the view graph to access segments + const auto& view = this->view_graph(); + + // Iterate through all segments in the shower + for (auto edesc : this->edges()) { + SegmentPtr seg = view[edesc].segment; + if (!seg) continue; + + // Check if this segment's cluster matches the start segment's cluster + if (seg->cluster() == start_cluster) { + num++; + } + } + + return num; + } + + int Shower::get_num_segments() { + return this->edges().size(); + } + + double Shower::get_total_length(){ + double total_length = 0; + + // Get the view graph to access segments + const auto& view = this->view_graph(); + + // Iterate through all segments in the shower + for (auto edesc : this->edges()) { + SegmentPtr seg = view[edesc].segment; + if (!seg) continue; + + // Add segment length + total_length += segment_track_length(seg); + } + + return total_length; + } + double Shower::get_total_length(Facade::Cluster* cluster){ + double total_length = 0; + + if (!cluster) { + return 0; + } + + // Get the view graph to access segments + const auto& view = this->view_graph(); + + // Iterate through all segments in the shower + for (auto edesc : this->edges()) { + SegmentPtr seg = view[edesc].segment; + if (!seg) continue; + + // Check if segment's cluster matches the input cluster + if (seg->cluster() == cluster) { + total_length += segment_track_length(seg); + } + } + + return total_length; + } + double Shower::get_total_track_length(){ + double total_length = 0; + + // Get the view graph to access segments + const auto& view = this->view_graph(); + + // Iterate through all segments in the shower + for (auto edesc : this->edges()) { + SegmentPtr seg = view[edesc].segment; + if (!seg) continue; + + // Only count segments that are NOT shower segments + // Check if segment has shower flags (kShowerTrajectory or kShowerTopology) + if (!seg->flags_any(SegmentFlags::kShowerTrajectory) && + !seg->flags_any(SegmentFlags::kShowerTopology)) { + total_length += segment_track_length(seg); + } + } + + return total_length; + } + + void Shower::update_particle_type(const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model){ + double track_length = 0; + double shower_length = 0; + + // Only process if there's more than one segment + if (this->edges().size() <= 1) { + return; + } + + // Get the view graph to access segments + const auto& view = this->view_graph(); + + // Iterate through all segments in the shower + for (auto edesc : this->edges()) { + SegmentPtr seg = view[edesc].segment; + if (!seg) continue; + + double length = segment_track_length(seg); + + // Check if segment is a shower segment OR not a proton (PDG 2212) + bool is_shower = seg->flags_any(SegmentFlags::kShowerTrajectory) || + seg->flags_any(SegmentFlags::kShowerTopology); + + bool is_not_proton = true; + if (seg->has_particle_info()) { + int pdg = seg->particle_info()->pdg(); + is_not_proton = (std::abs(pdg) != 2212); + } + + if (is_shower || is_not_proton) { + shower_length += length; + } else { + track_length += length; + } + } + + // If shower_length dominates, update start_segment to electron + if (shower_length > track_length && m_start_segment) { + // Calculate 4-momentum for electron (PDG = 11) + auto four_momentum = segment_cal_4mom(m_start_segment, 11, particle_data, recomb_model); + + // Create ParticleInfo for electron + auto pinfo = std::make_shared( + 11, // electron PDG + particle_data->get_particle_mass(11), // electron mass + particle_data->pdg_to_name(11), // "electron" + four_momentum // 4-momentum + ); + + // Store particle info in start_segment + m_start_segment->particle_info(pinfo); + } + } + + std::vector Shower::get_stem_dQ_dx(VertexPtr vertex, SegmentPtr segment, int limit /*=20*/){ + std::vector vec_dQ_dx; + const double MIP_dQdx = 43e3 / units::cm; + + if (!vertex || !segment) { + return vec_dQ_dx; + } + + // Get dQ and dx from segment's fits + const auto& fits = segment->fits(); + if (fits.empty()) { + return vec_dQ_dx; + } + + // Determine direction based on vertex position relative to segment + // Check if vertex is at the front of the segment + bool vertex_at_front = false; + if (!segment->wcpts().empty()) { + double d1 = WireCell::ray_length(WireCell::Ray{vertex->wcpt().point, segment->wcpts().front().point}); + double d2 = WireCell::ray_length(WireCell::Ray{vertex->wcpt().point, segment->wcpts().back().point}); + vertex_at_front = (d1 < d2); + } + + // Fill vec_dQ_dx based on direction + if (vertex_at_front) { + for (size_t i = 0; i < fits.size(); i++) { + double dQ_dx_normalized = fits[i].dQ / (fits[i].dx + 1e-9) / MIP_dQdx; + vec_dQ_dx.push_back(dQ_dx_normalized); + if (vec_dQ_dx.size() >= (size_t)limit) break; + } + } else { + for (int i = (int)fits.size() - 1; i >= 0; i--) { + double dQ_dx_normalized = fits[i].dQ / (fits[i].dx + 1e-9) / MIP_dQdx; + vec_dQ_dx.push_back(dQ_dx_normalized); + if (vec_dQ_dx.size() >= (size_t)limit) break; + } + } + + // If this is the start_segment and we don't have enough points, continue to next segments + if (segment == m_start_segment && vec_dQ_dx.size() < (size_t)limit) { + VertexPtr curr_vertex = vertex; + SegmentPtr curr_segment = segment; + int count = 0; + + while (vec_dQ_dx.size() < (size_t)limit && count < 3) { + // Find next vertex (the other end of current segment) + VertexPtr next_vertex = find_other_vertex(m_full_graph, curr_segment, curr_vertex); + if (!next_vertex) break; + + // Direction from current vertex to next vertex + WireCell::Vector dir1 = curr_vertex->fit().point - next_vertex->fit().point; + + // Find the next segment with largest angle + SegmentPtr next_segment = nullptr; + WireCell::Vector dir2; + double max_angle = 0; + + auto next_vdesc = next_vertex->get_descriptor(); + for (auto edesc : boost::make_iterator_range(boost::out_edges(next_vdesc, m_full_graph))) { + if (!has_edge(edesc)) continue; // Only consider edges in this view + + SegmentPtr seg = m_full_graph[edesc].segment; + if (seg == curr_segment) continue; + + WireCell::Vector tmp_dir = segment_cal_dir_3vector(seg, next_vertex->fit().point, 10 * units::cm); + double angle = std::acos(std::clamp(dir1.dot(tmp_dir) / (dir1.magnitude() * tmp_dir.magnitude() + 1e-9), -1.0, 1.0)) * 180.0 / M_PI; + + if (angle > max_angle) { + max_angle = angle; + next_segment = seg; + dir2 = tmp_dir; + } + } + + if (!next_segment) break; + + // Check if there are other segments that would make this "bad" + bool flag_bad = false; + for (auto edesc : boost::make_iterator_range(boost::out_edges(next_vdesc, m_full_graph))) { + if (!has_edge(edesc)) continue; + + SegmentPtr seg = m_full_graph[edesc].segment; + if (seg == curr_segment || seg == next_segment) continue; + + double seg_length = segment_track_length(seg); + if (seg_length > 3 * units::cm) { + WireCell::Vector tmp_dir = segment_cal_dir_3vector(seg, next_vertex->fit().point, 10 * units::cm); + double angle = std::acos(std::clamp(dir2.dot(tmp_dir) / (dir2.magnitude() * tmp_dir.magnitude() + 1e-9), -1.0, 1.0)) * 180.0 / M_PI; + if (angle < 25) { + flag_bad = true; + break; + } + } + } + + if (flag_bad) break; + + // Remove last element and add points from next segment + if (!vec_dQ_dx.empty()) { + vec_dQ_dx.pop_back(); + } + + const auto& next_fits = next_segment->fits(); + if (next_fits.empty()) break; + + // Determine direction for next segment + bool next_vertex_at_front = false; + if (!next_segment->wcpts().empty()) { + double d1 = WireCell::ray_length(WireCell::Ray{next_vertex->wcpt().point, next_segment->wcpts().front().point}); + double d2 = WireCell::ray_length(WireCell::Ray{next_vertex->wcpt().point, next_segment->wcpts().back().point}); + next_vertex_at_front = (d1 < d2); + } + + // Add dQ/dx from next segment + if (next_vertex_at_front) { + for (size_t i = 0; i < next_fits.size(); i++) { + double dQ_dx_normalized = next_fits[i].dQ / (next_fits[i].dx + 1e-9) / MIP_dQdx; + vec_dQ_dx.push_back(dQ_dx_normalized); + if (vec_dQ_dx.size() >= (size_t)limit) break; + } + } else { + for (int i = (int)next_fits.size() - 1; i >= 0; i--) { + double dQ_dx_normalized = next_fits[i].dQ / (next_fits[i].dx + 1e-9) / MIP_dQdx; + vec_dQ_dx.push_back(dQ_dx_normalized); + if (vec_dQ_dx.size() >= (size_t)limit) break; + } + } + + if (vec_dQ_dx.size() >= (size_t)limit) break; + + // Prepare for next iteration + curr_vertex = next_vertex; + curr_segment = next_segment; + count++; + } + } + + return vec_dQ_dx; + } + + void Shower::calculate_kinematics(const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model){ + int nsegments = this->edges().size(); + + if (nsegments == 1) { + // Single segment case + if (!m_start_segment) return; + + // Set particle type from start segment + if (m_start_segment->has_particle_info()) { + data.particle_type = m_start_segment->particle_info()->pdg(); + } + + // Check if shower + bool flag_shower = m_start_segment->flags_any(SegmentFlags::kShowerTrajectory) || + m_start_segment->flags_any(SegmentFlags::kShowerTopology); + + // Calculate energies + double seg_length = segment_track_length(m_start_segment); + data.kenergy_range = cal_kine_range(seg_length, data.particle_type, particle_data); + data.kenergy_dQdx = segment_cal_kine_dQdx(m_start_segment, recomb_model); + + // Calculate kenergy_best + if (data.start_connection_type == 1) { + data.kenergy_best = (seg_length < 4 * units::cm) ? data.kenergy_dQdx : data.kenergy_range; + } else { + if (flag_shower) { + data.kenergy_best = 0; + } else { + data.kenergy_best = (seg_length < 4 * units::cm) ? data.kenergy_dQdx : data.kenergy_range; + } + } + + // Calculate start_point and end_point + const auto& fits = m_start_segment->fits(); + if (data.start_connection_type == 1 || !this->dpcloud("fit")) { + if (!fits.empty()) { + if (m_start_segment->dirsign() == 1) { + data.start_point = fits.front().point; + data.end_point = fits.back().point; + } else if (m_start_segment->dirsign() == -1) { + data.start_point = fits.back().point; + data.end_point = fits.front().point; + } + } + } else { + if (m_start_vertex) { + data.start_point = shower_get_closest_point(*this, m_start_vertex->fit().point, "fit").second; + + // Find farthest vertex + double max_dis = 0; + const auto& view = this->view_graph(); + for (auto vdesc : this->nodes()) { + VertexPtr vtx = view[vdesc].vertex; + if (!vtx) continue; + double dis = (data.start_point - vtx->fit().point).magnitude(); + if (dis > max_dis) { + max_dis = dis; + data.end_point = vtx->fit().point; + } + } + } + } + + // Calculate init_dir + if (data.start_connection_type == 1) { + data.init_dir = segment_cal_dir_3vector(m_start_segment); + } else if (data.start_connection_type == 2 || data.start_connection_type == 3) { + if (m_start_vertex) { + data.init_dir = (data.start_point - m_start_vertex->fit().point).norm(); + } + } + + } else { + // Multiple segments case + if (!m_start_segment) return; + + // Get number of connected segments + auto [segs, verts] = get_connected_pieces(m_start_segment); + int nconnected_segs = segs.size(); + + // Set particle type + if (m_start_segment->has_particle_info()) { + data.particle_type = m_start_segment->particle_info()->pdg(); + } + + bool flag_shower = m_start_segment->flags_any(SegmentFlags::kShowerTrajectory) || + m_start_segment->flags_any(SegmentFlags::kShowerTopology); + + if (nsegments == nconnected_segs) { + // Single track (all connected) + + // Calculate start_point + const auto& fits = m_start_segment->fits(); + if (data.start_connection_type == 1 || !this->dpcloud("fit")) { + if (!fits.empty()) { + if (m_start_segment->dirsign() == 1) { + data.start_point = fits.front().point; + } else if (m_start_segment->dirsign() == -1) { + data.start_point = fits.back().point; + } + } + } else { + if (m_start_vertex) { + data.start_point = shower_get_closest_point(*this, m_start_vertex->fit().point, "fit").second; + } + } + + // Calculate init_dir + double seg_length = segment_track_length(m_start_segment); + if (data.start_connection_type == 1) { + if (seg_length > 8 * units::cm) { + data.init_dir = segment_cal_dir_3vector(m_start_segment); + } else if (m_start_vertex) { + data.init_dir = shower_cal_dir_3vector(*this, m_start_vertex->fit().point, 12 * units::cm); + } + } else if (data.start_connection_type == 2 || data.start_connection_type == 3) { + if (m_start_vertex) { + data.init_dir = (data.start_point - m_start_vertex->fit().point).norm(); + } + } + + // Find farthest vertex for end_point + double max_dis = 0; + const auto& view = this->view_graph(); + for (auto vdesc : this->nodes()) { + VertexPtr vtx = view[vdesc].vertex; + if (!vtx) continue; + double dis = (data.start_point - vtx->fit().point).magnitude(); + if (dis > max_dis) { + max_dis = dis; + data.end_point = vtx->fit().point; + } + } + + // Collect all dQ and dx from all segments + double total_length = 0; + std::vector vec_dQ, vec_dx; + for (auto edesc : this->edges()) { + SegmentPtr seg = view[edesc].segment; + if (!seg) continue; + + total_length += segment_track_length(seg); + + const auto& seg_fits = seg->fits(); + for (const auto& fit : seg_fits) { + vec_dQ.push_back(fit.dQ); + vec_dx.push_back(fit.dx); + } + } + + // Calculate energies + data.kenergy_range = cal_kine_range(total_length, data.particle_type, particle_data); + data.kenergy_dQdx = cal_kine_dQdx(vec_dQ, vec_dx, recomb_model); + + // Calculate kenergy_best + if (data.start_connection_type == 1) { + data.kenergy_best = (seg_length < 4 * units::cm) ? data.kenergy_dQdx : data.kenergy_range; + } else { + if (flag_shower) { + data.kenergy_best = 0; + } else { + data.kenergy_best = (seg_length < 4 * units::cm) ? data.kenergy_dQdx : data.kenergy_range; + } + } + + } else { + // Multiple tracks (not all connected) + + // Calculate start_point + const auto& fits = m_start_segment->fits(); + if (data.start_connection_type == 1 || !this->dpcloud("fit")) { + if (!fits.empty()) { + if (m_start_segment->dirsign() == 1) { + data.start_point = fits.front().point; + } else if (m_start_segment->dirsign() == -1) { + data.start_point = fits.back().point; + } + } + } else { + if (m_start_vertex) { + data.start_point = shower_get_closest_point(*this, m_start_vertex->fit().point, "fit").second; + } + } + + // Calculate init_dir + double seg_length = segment_track_length(m_start_segment); + if (data.start_connection_type == 1) { + if (seg_length > 8 * units::cm) { + data.init_dir = segment_cal_dir_3vector(m_start_segment); + } else if (m_start_vertex) { + data.init_dir = shower_cal_dir_3vector(*this, m_start_vertex->fit().point, 12 * units::cm); + } + } else if (data.start_connection_type == 2 || data.start_connection_type == 3) { + if (m_start_vertex) { + data.init_dir = (data.start_point - m_start_vertex->fit().point).norm(); + } + } + + // Find farthest vertex for end_point + double max_dis = 0; + const auto& view = this->view_graph(); + for (auto vdesc : this->nodes()) { + VertexPtr vtx = view[vdesc].vertex; + if (!vtx) continue; + double dis = (data.start_point - vtx->fit().point).magnitude(); + if (dis > max_dis) { + max_dis = dis; + data.end_point = vtx->fit().point; + } + } + + // Collect all dQ and dx from all segments + std::vector vec_dQ, vec_dx; + for (auto edesc : this->edges()) { + SegmentPtr seg = view[edesc].segment; + if (!seg) continue; + + const auto& seg_fits = seg->fits(); + for (const auto& fit : seg_fits) { + vec_dQ.push_back(fit.dQ); + vec_dx.push_back(fit.dx); + } + } + + // Calculate energies + data.kenergy_range = 0; + data.kenergy_dQdx = cal_kine_dQdx(vec_dQ, vec_dx, recomb_model); + data.kenergy_best = 0; + } + } + } + + void Shower::calculate_kinematics_long_muon(std::set& segments_in_muons, const Clus::ParticleDataSet::pointer& particle_data, const IRecombinationModel::pointer& recomb_model){ + // Get particle type from start segment + int particle_type = abs(m_start_segment->particle_info()->pdg()); + // double particle_mass = m_start_segment->particle_info()->mass(); + + unset_flags(ShowerFlags::kKinematics); + + // Calculate total length ONLY from segments in segments_in_muons + double total_length = 0; + for (auto edesc : this->edges()) { + if (!has_edge(edesc)) continue; + auto seg = view_graph()[edesc].segment; + if (segments_in_muons.find(seg) != segments_in_muons.end()) { + total_length += segment_track_length(seg); + } + } + + // Collect dQ and dx from ALL segments + std::vector vec_dQ; + std::vector vec_dx; + + for (auto edesc : this->edges()) { + if (!has_edge(edesc)) continue; + auto seg = view_graph()[edesc].segment; + + std::vector seg_dQ; + std::vector seg_dx; + + const auto& seg_fits = seg->fits(); + for (const auto& fit : seg_fits) { + vec_dQ.push_back(fit.dQ); + vec_dx.push_back(fit.dx); + } + + vec_dQ.insert(vec_dQ.end(), seg_dQ.begin(), seg_dQ.end()); + vec_dx.insert(vec_dx.end(), seg_dx.begin(), seg_dx.end()); + } + + // Calculate kinetic energies + data.kenergy_range = cal_kine_range(total_length, particle_type, particle_data); + data.kenergy_dQdx = cal_kine_dQdx(vec_dQ, vec_dx, recomb_model); + + // For long muon, use dQdx as best energy + data.kenergy_best = data.kenergy_dQdx; + + // Calculate initial direction from start segment + data.init_dir = segment_cal_dir_3vector(m_start_segment); + + // Set start point based on direction + auto& fits = m_start_segment->fits(); + int dirsign_val = m_start_segment->dirsign(); + if (dirsign_val == 1) { + data.start_point = fits.front().point; + } else { + data.start_point = fits.back().point; + } + + // Find farthest vertex that has at least one segment in segments_in_muons + double max_dis = 0; + VertexPtr farthest_vertex = nullptr; + + for (auto vdesc : this->nodes()) { + if (!has_node(vdesc)) continue; + auto vtx = view_graph()[vdesc].vertex; + + // Check if this vertex has at least one segment in segments_in_muons + bool flag_contain = false; + for (auto out_edge : boost::make_iterator_range(boost::out_edges(vdesc, m_full_graph))) { + if (!has_edge(out_edge)) continue; + auto seg = view_graph()[out_edge].segment; + if (segments_in_muons.find(seg) != segments_in_muons.end()) { + flag_contain = true; + break; + } + } + + if (flag_contain) { + double dis = (vtx->fit().point - data.start_point).magnitude(); + if (dis > max_dis) { + max_dis = dis; + farthest_vertex = vtx; + } + } + } + + // Set end point to the farthest vertex + if (farthest_vertex) { + data.end_point = farthest_vertex->fit().point; + } else { + // Fallback: use the other end of start segment + auto& fits = m_start_segment->fits(); + if (m_start_segment->dirsign() == 1) { + data.end_point = fits.back().point; + } else { + data.end_point = fits.front().point; + } + } + } -} +} \ No newline at end of file diff --git a/clus/src/PRShowerFunctions.cxx b/clus/src/PRShowerFunctions.cxx new file mode 100644 index 000000000..00e4d0e98 --- /dev/null +++ b/clus/src/PRShowerFunctions.cxx @@ -0,0 +1,180 @@ +#include "WireCellClus/PRShowerFunctions.h" +#include "WireCellClus/PRSegmentFunctions.h" +#include "WireCellClus/DynamicPointCloud.h" + +using namespace WireCell::Clus::PR; + +namespace WireCell::Clus::PR { + + std::pair shower_get_closest_point(Shower& shower, const WireCell::Point& point, const std::string& cloud_name /* = "fit" */){ + // Get the dynamic point cloud from the shower + auto pcloud = shower.get_pcloud(cloud_name); + + // If no point cloud exists, return invalid result + if (!pcloud) { + return std::make_pair(-1.0, WireCell::Point(0, 0, 0)); + } + + // Get the 3D KD-tree + auto& kd3d = pcloud->kd3d(); + + // Prepare query point + std::vector query = {point.x(), point.y(), point.z()}; + + // Find the nearest neighbor + auto results = kd3d.knn(1, query); + + // Check if we found a point + if (results.empty()) { + return std::make_pair(-1.0, WireCell::Point(0, 0, 0)); + } + + // Get the result + const size_t idx = results[0].first; + const double distance = sqrt(results[0].second); // KD-tree returns squared distance + + // Get the actual point from the point cloud + const auto& points = pcloud->get_points(); + const auto& closest_pt = points[idx]; + + return std::make_pair(distance, WireCell::Point(closest_pt.x, closest_pt.y, closest_pt.z)); + } + + double shower_get_closest_dis(Shower& shower, SegmentPtr seg, const std::string& cloud_name /* = "fit" */){ + // Get the first point from segment's DynamicPointCloud + auto seg_dpc = seg->dpcloud(cloud_name); + if (!seg_dpc) { + return -1.0; + } + + const auto& seg_points = seg_dpc->get_points(); + if (seg_points.empty()) { + return -1.0; + } + + // Use the first point from segment's point cloud + WireCell::Point first_point(seg_points.front().x, seg_points.front().y, seg_points.front().z); + + // Step 1: Get closest point in shower to the first point of the segment + WireCell::Point test_p = shower_get_closest_point(shower, first_point, cloud_name).second; + + // Step 2: Get closest point in segment to that point (uses segment's DPC with KD-tree) + test_p = segment_get_closest_point(seg, test_p, cloud_name).second; + + // Step 3: Get closest point in shower to that point + test_p = shower_get_closest_point(shower, test_p, cloud_name).second; + + // Step 4: Get closest distance in segment to that point + return segment_get_closest_point(seg, test_p, cloud_name).first; + } + + double shower_get_dis(Shower& shower, SegmentPtr seg, const std::string& cloud_name /* = "fit" */){ + double min_dis = 1e9; + WireCell::Point min_point; + + // Get the first point from the input segment's DynamicPointCloud + auto seg_dpc = seg->dpcloud(cloud_name); + if (!seg_dpc) { + return -1.0; + } + + const auto& seg_points = seg_dpc->get_points(); + if (seg_points.empty()) { + return -1.0; + } + + WireCell::Point test_p(seg_points.front().x, seg_points.front().y, seg_points.front().z); + + // Get the view graph to access segments + const auto& view = shower.view_graph(); + + // First iteration: find the closest point in any shower segment to test_p + for (auto edesc : shower.edges()) { + SegmentPtr sg = view[edesc].segment; + if (!sg) continue; + + auto results = segment_get_closest_point(sg, test_p, cloud_name); + if (results.first < min_dis) { + min_dis = results.first; + min_point = results.second; + } + } + + // Get closest point in input segment to that minimum point + auto results1 = segment_get_closest_point(seg, min_point, cloud_name); + test_p = results1.second; + + // Second iteration: find the closest distance from any shower segment to the new test point + for (auto edesc : shower.edges()) { + SegmentPtr sg = view[edesc].segment; + if (!sg) continue; + + auto results = segment_get_closest_point(sg, test_p, cloud_name); + if (results.first < min_dis) { + min_dis = results.first; + min_point = results.second; + } + } + + return min_dis; + } + + WireCell::Vector shower_cal_dir_3vector(Shower& shower, const WireCell::Point& p, double dis_cut /* = 15*units::cm */){ + WireCell::Point p_sum(0, 0, 0); + int ncount = 0; + + // Get the view graph to access segments + const auto& view = shower.view_graph(); + + // Loop through all segments in the shower + for (auto edesc : shower.edges()) { + SegmentPtr seg = view[edesc].segment; + if (!seg) continue; + + // Get the segment's fits + const auto& fits = seg->fits(); + + // Check each fit point in the segment + for (const auto& fit : fits) { + // Calculate distance from this fit point to p + double dis = sqrt(pow(fit.point.x() - p.x(), 2) + + pow(fit.point.y() - p.y(), 2) + + pow(fit.point.z() - p.z(), 2)); + + // If within distance cutoff, accumulate the point + if (dis < dis_cut) { + p_sum = WireCell::Point(p_sum.x() + fit.point.x(), + p_sum.y() + fit.point.y(), + p_sum.z() + fit.point.z()); + ncount++; + } + } + } + + // If no points were found, return zero vector + if (ncount == 0) { + return WireCell::Vector(0, 0, 0); + } + + // Calculate the average point and direction vector + WireCell::Point p_avg(p_sum.x() / ncount, + p_sum.y() / ncount, + p_sum.z() / ncount); + + // Direction vector from p to average point + WireCell::Vector dir(p_avg.x() - p.x(), + p_avg.y() - p.y(), + p_avg.z() - p.z()); + + // Normalize the vector + double magnitude = dir.magnitude(); + if (magnitude > 0) { + dir = dir.norm(); + } + + return dir; + } + + + +} // namespace WireCell::Clus::PR diff --git a/clus/src/PatternDebugIO.cxx b/clus/src/PatternDebugIO.cxx new file mode 100644 index 000000000..9b0210c60 --- /dev/null +++ b/clus/src/PatternDebugIO.cxx @@ -0,0 +1,583 @@ +#include "WireCellClus/PatternDebugIO.h" +#include "WireCellClus/ClusteringFuncs.h" // Flags::main_cluster +#include "WireCellClus/Graphs.h" +#include "WireCellIface/WirePlaneId.h" +#include "WireCellUtil/Persist.h" +#include "WireCellUtil/PointCloudDataset.h" +#include "WireCellUtil/Logging.h" + +#include +#include +#include + +using namespace WireCell; +using namespace WireCell::Clus; +using namespace WireCell::PointCloud; + +static spdlog::logger& logger() { + static auto log = Log::logger("debugio"); + return *log; +} + +// Helper: serialize a coordinate array from a Dataset to JSON +static Json::Value pc_coords_to_json(const Dataset& ds, const std::vector& coord_names) +{ + Json::Value jpc(Json::objectValue); + for (const auto& name : coord_names) { + auto arr = ds.get(name); + if (!arr) continue; + auto elems = arr->elements(); + Json::Value jarr(Json::arrayValue); + for (double v : elems) { + jarr.append(v); + } + jpc[name] = jarr; + } + return jpc; +} + +// Helper: serialize a steiner graph to JSON +static Json::Value graph_to_json(const Graphs::Weighted::Graph& graph) +{ + Json::Value jg(Json::objectValue); + jg["num_vertices"] = static_cast(boost::num_vertices(graph)); + + auto weight_map = boost::get(boost::edge_weight, graph); + Json::Value jedges(Json::arrayValue); + auto [ebegin, eend] = boost::edges(graph); + for (auto eit = ebegin; eit != eend; ++eit) { + Json::Value edge(Json::arrayValue); + edge.append(static_cast(boost::source(*eit, graph))); + edge.append(static_cast(boost::target(*eit, graph))); + edge.append(boost::get(weight_map, *eit)); + jedges.append(edge); + } + jg["edges"] = jedges; + return jg; +} + +// Helper: serialize TrackFitting::Parameters to JSON +static Json::Value params_to_json(const TrackFitting::Parameters& p) +{ + Json::Value j(Json::objectValue); + j["DL"] = p.DL; + j["DT"] = p.DT; + j["col_sigma_w_T"] = p.col_sigma_w_T; + j["ind_sigma_u_T"] = p.ind_sigma_u_T; + j["ind_sigma_v_T"] = p.ind_sigma_v_T; + j["rel_uncer_ind"] = p.rel_uncer_ind; + j["rel_uncer_col"] = p.rel_uncer_col; + j["add_uncer_ind"] = p.add_uncer_ind; + j["add_uncer_col"] = p.add_uncer_col; + j["add_sigma_L"] = p.add_sigma_L; + j["rel_charge_uncer"] = p.rel_charge_uncer; + j["add_charge_uncer"] = p.add_charge_uncer; + j["default_charge_th"] = p.default_charge_th; + j["default_charge_err"] = p.default_charge_err; + j["scaling_quality_th"] = p.scaling_quality_th; + j["scaling_ratio"] = p.scaling_ratio; + j["area_ratio1"] = p.area_ratio1; + j["area_ratio2"] = p.area_ratio2; + j["skip_default_ratio_1"] = p.skip_default_ratio_1; + j["skip_ratio_cut"] = p.skip_ratio_cut; + j["skip_ratio_1_cut"] = p.skip_ratio_1_cut; + j["skip_angle_cut_1"] = p.skip_angle_cut_1; + j["skip_angle_cut_2"] = p.skip_angle_cut_2; + j["skip_angle_cut_3"] = p.skip_angle_cut_3; + j["skip_dis_cut"] = p.skip_dis_cut; + j["default_dQ_dx"] = p.default_dQ_dx; + j["end_point_factor"] = p.end_point_factor; + j["mid_point_factor"] = p.mid_point_factor; + j["nlevel"] = p.nlevel; + j["charge_cut"] = p.charge_cut; + j["low_dis_limit"] = p.low_dis_limit; + j["end_point_limit"] = p.end_point_limit; + j["time_tick_cut"] = p.time_tick_cut; + j["share_charge_err"] = p.share_charge_err; + j["min_drift_time"] = p.min_drift_time; + j["search_range"] = p.search_range; + j["dead_ind_weight"] = p.dead_ind_weight; + j["dead_col_weight"] = p.dead_col_weight; + j["close_ind_weight"] = p.close_ind_weight; + j["close_col_weight"] = p.close_col_weight; + j["overlap_th"] = p.overlap_th; + j["dx_norm_length"] = p.dx_norm_length; + j["lambda"] = p.lambda; + j["div_sigma"] = p.div_sigma; + return j; +} + +// Helper: deserialize TrackFitting::Parameters from JSON +static TrackFitting::Parameters params_from_json(const Json::Value& j) +{ + TrackFitting::Parameters p; + if (j.isMember("DL")) p.DL = j["DL"].asDouble(); + if (j.isMember("DT")) p.DT = j["DT"].asDouble(); + if (j.isMember("col_sigma_w_T")) p.col_sigma_w_T = j["col_sigma_w_T"].asDouble(); + if (j.isMember("ind_sigma_u_T")) p.ind_sigma_u_T = j["ind_sigma_u_T"].asDouble(); + if (j.isMember("ind_sigma_v_T")) p.ind_sigma_v_T = j["ind_sigma_v_T"].asDouble(); + if (j.isMember("rel_uncer_ind")) p.rel_uncer_ind = j["rel_uncer_ind"].asDouble(); + if (j.isMember("rel_uncer_col")) p.rel_uncer_col = j["rel_uncer_col"].asDouble(); + if (j.isMember("add_uncer_ind")) p.add_uncer_ind = j["add_uncer_ind"].asDouble(); + if (j.isMember("add_uncer_col")) p.add_uncer_col = j["add_uncer_col"].asDouble(); + if (j.isMember("add_sigma_L")) p.add_sigma_L = j["add_sigma_L"].asDouble(); + if (j.isMember("rel_charge_uncer")) p.rel_charge_uncer = j["rel_charge_uncer"].asDouble(); + if (j.isMember("add_charge_uncer")) p.add_charge_uncer = j["add_charge_uncer"].asDouble(); + if (j.isMember("default_charge_th")) p.default_charge_th = j["default_charge_th"].asDouble(); + if (j.isMember("default_charge_err")) p.default_charge_err = j["default_charge_err"].asDouble(); + if (j.isMember("scaling_quality_th")) p.scaling_quality_th = j["scaling_quality_th"].asDouble(); + if (j.isMember("scaling_ratio")) p.scaling_ratio = j["scaling_ratio"].asDouble(); + if (j.isMember("area_ratio1")) p.area_ratio1 = j["area_ratio1"].asDouble(); + if (j.isMember("area_ratio2")) p.area_ratio2 = j["area_ratio2"].asDouble(); + if (j.isMember("skip_default_ratio_1")) p.skip_default_ratio_1 = j["skip_default_ratio_1"].asDouble(); + if (j.isMember("skip_ratio_cut")) p.skip_ratio_cut = j["skip_ratio_cut"].asDouble(); + if (j.isMember("skip_ratio_1_cut")) p.skip_ratio_1_cut = j["skip_ratio_1_cut"].asDouble(); + if (j.isMember("skip_angle_cut_1")) p.skip_angle_cut_1 = j["skip_angle_cut_1"].asDouble(); + if (j.isMember("skip_angle_cut_2")) p.skip_angle_cut_2 = j["skip_angle_cut_2"].asDouble(); + if (j.isMember("skip_angle_cut_3")) p.skip_angle_cut_3 = j["skip_angle_cut_3"].asDouble(); + if (j.isMember("skip_dis_cut")) p.skip_dis_cut = j["skip_dis_cut"].asDouble(); + if (j.isMember("default_dQ_dx")) p.default_dQ_dx = j["default_dQ_dx"].asDouble(); + if (j.isMember("end_point_factor")) p.end_point_factor = j["end_point_factor"].asDouble(); + if (j.isMember("mid_point_factor")) p.mid_point_factor = j["mid_point_factor"].asDouble(); + if (j.isMember("nlevel")) p.nlevel = j["nlevel"].asInt(); + if (j.isMember("charge_cut")) p.charge_cut = j["charge_cut"].asDouble(); + if (j.isMember("low_dis_limit")) p.low_dis_limit = j["low_dis_limit"].asDouble(); + if (j.isMember("end_point_limit")) p.end_point_limit = j["end_point_limit"].asDouble(); + if (j.isMember("time_tick_cut")) p.time_tick_cut = j["time_tick_cut"].asDouble(); + if (j.isMember("share_charge_err")) p.share_charge_err = j["share_charge_err"].asDouble(); + if (j.isMember("min_drift_time")) p.min_drift_time = j["min_drift_time"].asDouble(); + if (j.isMember("search_range")) p.search_range = j["search_range"].asDouble(); + if (j.isMember("dead_ind_weight")) p.dead_ind_weight = j["dead_ind_weight"].asDouble(); + if (j.isMember("dead_col_weight")) p.dead_col_weight = j["dead_col_weight"].asDouble(); + if (j.isMember("close_ind_weight")) p.close_ind_weight = j["close_ind_weight"].asDouble(); + if (j.isMember("close_col_weight")) p.close_col_weight = j["close_col_weight"].asDouble(); + if (j.isMember("overlap_th")) p.overlap_th = j["overlap_th"].asDouble(); + if (j.isMember("dx_norm_length")) p.dx_norm_length = j["dx_norm_length"].asDouble(); + if (j.isMember("lambda")) p.lambda = j["lambda"].asDouble(); + if (j.isMember("div_sigma")) p.div_sigma = j["div_sigma"].asDouble(); + return p; +} + +// Helper: dump one cluster's steiner data to JSON +static Json::Value dump_cluster_data(const Facade::Cluster& cluster) +{ + Json::Value jcluster(Json::objectValue); + + // Scope coordinate names + const auto& scope = cluster.get_default_scope(); + Json::Value jcoords(Json::arrayValue); + for (const auto& c : scope.coords) { + jcoords.append(c); + } + jcluster["scope_coords"] = jcoords; + + // Steiner point cloud + if (cluster.has_pc("steiner_pc")) { + const auto& spc = cluster.get_pc("steiner_pc"); + jcluster["steiner_pc"] = pc_coords_to_json(spc, scope.coords); + jcluster["steiner_pc"]["npoints"] = static_cast(spc.size_major()); + + // Also dump flag_steiner_terminal if present + auto terminal_arr = spc.get("flag_steiner_terminal"); + if (terminal_arr) { + auto elems = terminal_arr->elements(); + Json::Value jterm(Json::arrayValue); + for (int v : elems) { + jterm.append(v); + } + jcluster["steiner_pc"]["flag_steiner_terminal"] = jterm; + } + + // Dump wpid array (WirePlaneId stored as int) + auto wpid_arr = spc.get("wpid"); + if (wpid_arr) { + auto elems = wpid_arr->elements(); + Json::Value jwpid(Json::arrayValue); + for (const auto& wpid : elems) { + jwpid.append(wpid.ident()); + } + jcluster["steiner_pc"]["wpid"] = jwpid; + } + } + + // Steiner graph + if (cluster.has_graph("steiner_graph")) { + const auto& graph = cluster.get_graph("steiner_graph"); + jcluster["steiner_graph"] = graph_to_json(graph); + } + + // Flags + Json::Value jflags(Json::objectValue); + jflags["main_cluster"] = cluster.get_flag(Facade::Flags::main_cluster); + jcluster["flags"] = jflags; + + // Scope transform name (e.g. "Unity", "T0Correction") + jcluster["scope_transform"] = cluster.get_scope_transform(); + + // Blob 3d point clouds: dump ALL arrays from each blob's "3d" PC + // (not just coordinate arrays - we need corrected arrays like x_t0cor too) + Json::Value jblobs(Json::arrayValue); + try { + for (const auto* blob : cluster.children()) { + const auto& lpcs = blob->node()->value.local_pcs(); + auto it3d = lpcs.find("3d"); + if (it3d == lpcs.end()) continue; + + const auto& ds = it3d->second; + Json::Value jblob_3d(Json::objectValue); + Json::Value jarray_types(Json::objectValue); + for (const auto& key : ds.keys()) { + auto arr = ds.get(key); + if (!arr) continue; + jarray_types[key] = arr->dtype(); + Json::Value jarr(Json::arrayValue); + if (arr->is_type()) { + for (double v : arr->elements()) jarr.append(v); + } else if (arr->is_type()) { + for (float v : arr->elements()) jarr.append(v); + } else if (arr->is_type()) { + for (int v : arr->elements()) jarr.append(v); + } else { + // Fallback: try double + for (double v : arr->elements()) jarr.append(v); + } + jblob_3d[key] = jarr; + } + jblob_3d["_array_types"] = jarray_types; + + // Also dump scalar PC for this blob + auto it_sc = lpcs.find("scalar"); + if (it_sc != lpcs.end()) { + const auto& sc_ds = it_sc->second; + Json::Value jscalar(Json::objectValue); + Json::Value jsc_types(Json::objectValue); + for (const auto& key : sc_ds.keys()) { + auto arr = sc_ds.get(key); + if (!arr) continue; + jsc_types[key] = arr->dtype(); + Json::Value jarr(Json::arrayValue); + if (arr->is_type()) { + for (double v : arr->elements()) jarr.append(v); + } else if (arr->is_type()) { + for (float v : arr->elements()) jarr.append(v); + } else if (arr->is_type()) { + for (int v : arr->elements()) jarr.append(v); + } else { + for (double v : arr->elements()) jarr.append(v); + } + jscalar[key] = jarr; + } + jscalar["_array_types"] = jsc_types; + jblob_3d["_scalar"] = jscalar; + } + + jblobs.append(jblob_3d); + } + } catch (const std::exception& e) { + logger().warn("Could not dump blob 3d PCs: {}", e.what()); + } + jcluster["blobs"] = jblobs; + + return jcluster; +} + + +void PR::DebugIO::dump_init_first_segment_inputs( + const std::string& output_path, + const Facade::Cluster& cluster, + const Facade::Cluster* main_cluster, + bool flag_back_search, + const TrackFitting& track_fitter) +{ + Json::Value root(Json::objectValue); + + root["cluster"] = dump_cluster_data(cluster); + root["flag_back_search"] = flag_back_search; + root["trackfitting_params"] = params_to_json(track_fitter.get_parameters()); + + // Precompute boundary steiner indices (needs anode, which is available now) + try { + auto boundary = cluster.get_two_boundary_steiner_graph_idx( + "steiner_graph", "steiner_pc"); + root["boundary_steiner_indices"] = Json::Value(Json::arrayValue); + root["boundary_steiner_indices"].append(static_cast(boundary.first)); + root["boundary_steiner_indices"].append(static_cast(boundary.second)); + } catch (const std::exception& e) { + logger().warn("Could not dump boundary steiner indices: {}", e.what()); + } + + // Main cluster (only if it differs from cluster) + if (main_cluster && main_cluster != &cluster) { + root["main_cluster"] = dump_cluster_data(*main_cluster); + } + + Persist::dump(output_path, root, true); + logger().info("Dumped init_first_segment inputs to {}", output_path); +} + + +// Helper: reconstruct a Dataset from JSON coordinate arrays +static Dataset json_to_dataset(const Json::Value& jpc, const std::vector& coord_names) +{ + std::map arrays; + for (const auto& name : coord_names) { + if (!jpc.isMember(name)) continue; + const auto& jarr = jpc[name]; + std::vector vals; + vals.reserve(jarr.size()); + for (const auto& v : jarr) { + vals.push_back(v.asDouble()); + } + arrays.emplace(name, Array(vals)); + } + + // Also restore flag_steiner_terminal if present + if (jpc.isMember("flag_steiner_terminal")) { + const auto& jterm = jpc["flag_steiner_terminal"]; + std::vector vals; + vals.reserve(jterm.size()); + for (const auto& v : jterm) { + vals.push_back(v.asInt()); + } + arrays.emplace("flag_steiner_terminal", Array(vals)); + } + + // Restore wpid array (WirePlaneId is same size as int) + if (jpc.isMember("wpid")) { + const auto& jwpid = jpc["wpid"]; + std::vector vals; + vals.reserve(jwpid.size()); + for (const auto& v : jwpid) { + vals.push_back(v.asInt()); + } + arrays.emplace("wpid", Array(vals)); + } + + return Dataset(arrays); +} + +// Helper: reconstruct a graph from JSON +static Graphs::Weighted::Graph json_to_graph(const Json::Value& jg) +{ + size_t nv = jg["num_vertices"].asUInt(); + Graphs::Weighted::Graph graph(nv); + + const auto& jedges = jg["edges"]; + for (const auto& edge : jedges) { + auto src = edge[0].asUInt(); + auto tgt = edge[1].asUInt(); + double weight = edge[2].asDouble(); + boost::add_edge(src, tgt, weight, graph); + } + return graph; +} + +// Helper: create a minimal cluster node with blob child containing a "3d" PC +// and inject steiner data. Returns the cluster node (raw ptr, owned by parent). +static PointCloud::Tree::Points::node_t* make_cluster_node( + PointCloud::Tree::Points::node_t& grouping_node, + const Json::Value& jcluster) +{ + // Read scope coords + std::vector coord_names; + if (jcluster.isMember("scope_coords")) { + for (const auto& c : jcluster["scope_coords"]) { + coord_names.push_back(c.asString()); + } + } + if (coord_names.empty()) { + coord_names = {"x", "y", "z"}; + } + + // Build the cluster subtree: cluster_node -> blob_node(s) + // Each blob needs a "3d" PC with ALL arrays (including corrected coords like x_t0cor) + // and a "scalar" PC with blob metadata. + auto cluster_ptr = std::make_unique(); + + if (jcluster.isMember("blobs") && jcluster["blobs"].size() > 0) { + // Restore real blob data from dump + for (const auto& jblob_3d : jcluster["blobs"]) { + // Reconstruct "3d" Dataset with all arrays + std::map blob_arrays; + Json::Value jtypes = jblob_3d.get("_array_types", Json::objectValue); + for (const auto& key : jblob_3d.getMemberNames()) { + if (key == "_array_types" || key == "_scalar") continue; + const auto& jarr = jblob_3d[key]; + if (!jarr.isArray()) continue; + + std::string dtype = jtypes.get(key, "f8").asString(); + // dtypes are NumPy-style: "f4"=float, "f8"=double, "i4"=int, etc. + if (dtype == "f4") { + std::vector vals; + vals.reserve(jarr.size()); + for (const auto& v : jarr) vals.push_back(v.asFloat()); + blob_arrays.emplace(key, Array(vals)); + } else if (dtype[0] == 'i') { + std::vector vals; + vals.reserve(jarr.size()); + for (const auto& v : jarr) vals.push_back(v.asInt()); + blob_arrays.emplace(key, Array(vals)); + } else { + // "f8", "d", or anything else -> double + std::vector vals; + vals.reserve(jarr.size()); + for (const auto& v : jarr) vals.push_back(v.asDouble()); + blob_arrays.emplace(key, Array(vals)); + } + } + Dataset blob_3d_ds(blob_arrays); + + // Reconstruct "scalar" Dataset + std::map scalar_arrays; + if (jblob_3d.isMember("_scalar")) { + const auto& jscalar = jblob_3d["_scalar"]; + Json::Value jsc_types = jscalar.get("_array_types", Json::objectValue); + for (const auto& key : jscalar.getMemberNames()) { + if (key == "_array_types") continue; + const auto& jarr = jscalar[key]; + if (!jarr.isArray()) continue; + + std::string dtype = jsc_types.get(key, "f8").asString(); + // dtypes are NumPy-style: "f4"=float, "f8"=double, "i4"=int, etc. + if (dtype == "f4") { + std::vector vals; + vals.reserve(jarr.size()); + for (const auto& v : jarr) vals.push_back(v.asFloat()); + scalar_arrays.emplace(key, Array(vals)); + } else if (dtype[0] == 'i') { + std::vector vals; + vals.reserve(jarr.size()); + for (const auto& v : jarr) vals.push_back(v.asInt()); + scalar_arrays.emplace(key, Array(vals)); + } else { + // "f8", "d", or anything else -> double + std::vector vals; + vals.reserve(jarr.size()); + for (const auto& v : jarr) vals.push_back(v.asDouble()); + scalar_arrays.emplace(key, Array(vals)); + } + } + } + Dataset scalar_ds(scalar_arrays); + + cluster_ptr->insert( + PointCloud::Tree::Points({ + {"scalar", std::move(scalar_ds)}, + {"3d", std::move(blob_3d_ds)}, + })); + } + } else { + // Fallback: create a minimal dummy blob with one point + using fa_float_t = Facade::float_t; + using fa_int_t = Facade::int_t; + std::map blob_arrays; + for (const auto& name : coord_names) { + blob_arrays.emplace(name, Array(std::vector{0.0})); + } + Dataset scalar_ds({ + {"charge", Array(std::vector{1.0})}, + {"center_x", Array(std::vector{0.0})}, + {"center_y", Array(std::vector{0.0})}, + {"center_z", Array(std::vector{0.0})}, + {"wpid", Array(std::vector{0})}, + {"npoints", Array(std::vector{1})}, + {"slice_index_min", Array(std::vector{0})}, + {"slice_index_max", Array(std::vector{1})}, + {"u_wire_index_min", Array(std::vector{0})}, + {"u_wire_index_max", Array(std::vector{1})}, + {"v_wire_index_min", Array(std::vector{0})}, + {"v_wire_index_max", Array(std::vector{1})}, + {"w_wire_index_min", Array(std::vector{0})}, + {"w_wire_index_max", Array(std::vector{1})}, + {"max_wire_interval", Array(std::vector{1})}, + {"min_wire_interval", Array(std::vector{1})}, + {"max_wire_type", Array(std::vector{0})}, + {"min_wire_type", Array(std::vector{0})}, + }); + Dataset blob_3d_ds(blob_arrays); + cluster_ptr->insert( + PointCloud::Tree::Points({ + {"scalar", std::move(scalar_ds)}, + {"3d", std::move(blob_3d_ds)}, + })); + } + + // Insert cluster into grouping + auto* cluster_node = grouping_node.insert(std::move(cluster_ptr)); + + // Get the Cluster facade + auto* cluster = cluster_node->value.facade(); + + // Inject steiner_pc into cluster's local_pcs + if (jcluster.isMember("steiner_pc")) { + auto steiner_ds = json_to_dataset(jcluster["steiner_pc"], coord_names); + cluster->local_pcs()["steiner_pc"] = std::move(steiner_ds); + } + + // Inject steiner_graph + if (jcluster.isMember("steiner_graph")) { + auto graph = json_to_graph(jcluster["steiner_graph"]); + cluster->give_graph("steiner_graph", std::move(graph)); + } + + // Set flags + if (jcluster.isMember("flags")) { + const auto& jflags = jcluster["flags"]; + if (jflags.isMember("main_cluster") && jflags["main_cluster"].asInt()) { + cluster->set_flag(Facade::Flags::main_cluster, 1); + } + } + + // Directly set the default scope with the dumped coordinate names. + PointCloud::Tree::Scope loaded_scope{"3d", coord_names}; + cluster->set_default_scope(loaded_scope); + + // Restore the scope transform mapping so get_scope_transform() returns + // the correct name (e.g. "T0Correction") instead of defaulting to "Unity". + if (jcluster.isMember("scope_transform")) { + std::string transform_name = jcluster["scope_transform"].asString(); + cluster->set_scope_transform(loaded_scope, transform_name); + } + + return cluster_node; +} + + +PR::DebugIO::LoadedTestData +PR::DebugIO::load_init_first_segment_inputs(const std::string& input_path) +{ + auto root = Persist::load(input_path); + + LoadedTestData data; + data.flag_back_search = root.get("flag_back_search", true).asBool(); + + if (root.isMember("trackfitting_params")) { + data.trackfitting_params = params_from_json(root["trackfitting_params"]); + } + + // Precomputed boundary indices + if (root.isMember("boundary_steiner_indices")) { + const auto& jbi = root["boundary_steiner_indices"]; + data.boundary_steiner_indices = {jbi[0].asUInt(), jbi[1].asUInt()}; + } + + // Create grouping node (root of the tree) + data.grouping_node = std::make_unique(); + auto* grouping = data.grouping_node->value.facade(); + (void)grouping; + + // Create the main cluster + auto* cluster_node = make_cluster_node(*data.grouping_node, root["cluster"]); + data.cluster = cluster_node->value.facade(); + + // Create main_cluster (separate or same as cluster) + if (root.isMember("main_cluster")) { + auto* main_cluster_node = make_cluster_node(*data.grouping_node, root["main_cluster"]); + data.main_cluster = main_cluster_node->value.facade(); + } else { + data.main_cluster = data.cluster; + } + + logger().info("Loaded init_first_segment inputs from {}", input_path); + return data; +} diff --git a/clus/src/TaggerCheckNeutrino.cxx b/clus/src/TaggerCheckNeutrino.cxx new file mode 100644 index 000000000..0b1c6b5f4 --- /dev/null +++ b/clus/src/TaggerCheckNeutrino.cxx @@ -0,0 +1,133 @@ +#include "WireCellClus/TaggerCheckNeutrino.h" +#include "WireCellClus/NeutrinoPatternBase.h" // pattern recognition ... +#include "WireCellClus/PatternDebugIO.h" // debug dump/load + +#include + +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; + } + } + + // Debug dump (only when env var is set) + if (main_cluster) { + if (const char* dump_path = std::getenv("WCT_DUMP_INIT_FIRST_SEGMENT")) { + DebugIO::dump_init_first_segment_inputs( + dump_path, *main_cluster, main_cluster, true, *m_track_fitter); + } + } + + // Create PRGraph and first segment + 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); + + m_track_fitter->do_multi_tracking(true, true, true); + + + // Store TrackFitting in the grouping for later access by bee output and tracking sink + grouping.set_track_fitting(m_track_fitter); +} + +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 2b13eac0f..a09acb282 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 5767b8860..cc93560e6 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){ @@ -262,6 +264,12 @@ void TrackFitting::add_graph(std::shared_ptr graph){ std::cout << "TrackFitting: Added graph with " << segments_set.size() << " segments." << " " << m_clusters.size() << " " << m_blobs.size() << std::endl; } +void TrackFitting::add_cluster(std::shared_ptr cluster){ + m_clusters.insert(cluster.get()); + for (auto& blob: cluster->children()){ + m_blobs.insert(blob); + } +} void TrackFitting::add_segment(std::shared_ptr segment){ m_segments.insert(segment); @@ -732,6 +740,64 @@ void TrackFitting::prepare_data() { // } } +void TrackFitting::collect_2D_charge(std::map& charge_2d_u, std::map& charge_2d_v, std::map& charge_2d_w, std::map, std::vector>>& map_apa_ch_plane_wires){ + + // Clear output maps + charge_2d_u.clear(); + charge_2d_v.clear(); + charge_2d_w.clear(); + map_apa_ch_plane_wires.clear(); + + // Track which (apa, channel) pairs we've already processed for geometry map + // This avoids redundant lookups since geometry doesn't depend on time + std::set> processed_apa_channel; + + // Step 1: Iterate through m_charge_data once to: + // a) Divide charges into U/V/W maps based on plane + // b) Collect unique (apa, channel) pairs for geometry processing + for (const auto& [coord_key, charge_measurement] : m_charge_data) { + int apa = coord_key.apa; + int channel = coord_key.channel; + + // Mark this (apa, channel) for geometry processing + auto apa_ch_pair = std::make_pair(apa, channel); + processed_apa_channel.insert(apa_ch_pair); + + // Get wire information for this channel to determine plane + auto wire_info = get_wires_for_channel(apa, channel); + + // Classify charge data by plane and store in appropriate map + // A channel can map to multiple wires (wrapped wires), but they should be on same plane + for (const auto& [face, plane, wire] : wire_info) { + // Store in appropriate plane map based on plane index + // Assuming plane: 0=U, 1=V, 2=W + if (plane == 0) { + charge_2d_u[coord_key] = charge_measurement; + } else if (plane == 1) { + charge_2d_v[coord_key] = charge_measurement; + } else if (plane == 2) { + charge_2d_w[coord_key] = charge_measurement; + } + + // Only need to categorize once per channel + break; + } + } + + // Step 2: Build geometry map efficiently - only process unique (apa, channel) pairs + // This is time-independent, so we only need to do this once per channel + for (const auto& apa_ch_pair : processed_apa_channel) { + int apa = apa_ch_pair.first; + int channel = apa_ch_pair.second; + + // Get all wires for this channel (handles wrapped wires) + auto wire_info = get_wires_for_channel(apa, channel); + + // Store in map: (apa, channel) -> vector of (face, plane, wire) + map_apa_ch_plane_wires[apa_ch_pair] = wire_info; + } +} + void TrackFitting::fill_global_rb_map() { // Clear the global readout map first if (global_rb_map.size() != 0 ) return; @@ -801,7 +867,67 @@ void TrackFitting::fill_global_rb_map() { } std::cout << "Global RB Map filled with " << global_rb_map.size() << " coordinate entries." << std::endl; -} +} + +void TrackFitting::fill_fitted_charge_2d( + const std::map>>& map_U, + const std::map>>& map_V, + const std::map>>& map_W, + const Eigen::VectorXd& pred_u, const Eigen::VectorXd& pred_v, const Eigen::VectorXd& pred_w, + double rel_uncer_ind, double rel_uncer_col, + double add_uncer_ind, double add_uncer_col) +{ + m_fitted_charge_2d.clear(); + + // Lambda to process one plane + auto process_plane = [&](const std::map>>& plane_map, + const Eigen::VectorXd& pred_data, + int plane_idx, + double rel_uncer, double add_uncer) { + int idx = 0; + for (const auto& [coord_key, result] : plane_map) { + const auto& measurement = result.first; + const auto& coord_2d_set = result.second; + + // Un-whiten predicted charge + double pred_charge = 0; + if (measurement.charge > 0 && measurement.flag != 0) { + double total_err = sqrt(pow(measurement.charge_err, 2) + + pow(measurement.charge * rel_uncer, 2) + + pow(add_uncer, 2)); + pred_charge = pred_data(idx) * total_err; + } + + // Get cluster associations from global_rb_map + std::set clusters; + auto rb_it = global_rb_map.find(coord_key); + if (rb_it != global_rb_map.end()) { + for (auto* blob : rb_it->second) { + auto* cl = blob->cluster(); + if (cl) clusters.insert(cl); + } + } + + // Store for each Coord2D (handles wrapped wires with multiple face/wire) + for (const auto& c2d : coord_2d_set) { + APAFacePlane afp{c2d.apa, c2d.face, plane_idx}; + WireTime wt{c2d.wire, c2d.time}; + auto& entry = m_fitted_charge_2d[afp][wt]; + entry.charge = measurement.charge; + entry.charge_err = measurement.charge_err; + entry.pred_charge = pred_charge; + entry.flag = measurement.flag; + entry.clusters = clusters; + } + + idx++; + } + }; + + process_plane(map_U, pred_u, 0, rel_uncer_ind, add_uncer_ind); + process_plane(map_V, pred_v, 1, rel_uncer_ind, add_uncer_ind); + process_plane(map_W, pred_w, 2, rel_uncer_col, add_uncer_col); +} // ============================================================================ // Helper functions for organize_segments_path methods @@ -831,7 +957,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 +1016,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 +1055,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 +1398,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 +1512,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 +1836,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 +2057,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 +2096,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 +2215,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 +2229,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 +2488,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 +2887,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 +2906,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 +2938,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 +2957,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 +2975,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 +3038,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 +3065,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 +3113,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 +3640,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 +3711,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 +3719,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 +3731,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 +4263,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 +4317,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 +4362,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 +4430,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 +4458,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 +4486,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 +4985,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 +5121,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 +5376,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 +5432,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 +5458,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 +5471,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 +5528,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 +5544,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 +5577,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 +5587,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 +5615,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 +5686,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 +5709,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,31 +5821,386 @@ 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... - } - } - - // Build connected_vec for regularization - std::vector> connected_vec(n_3D_pos); - for (auto e_it = edge_range.first; e_it != edge_range.second; ++e_it) { - auto& edge_bundle = (*m_graph)[*e_it]; - if (!edge_bundle.segment) continue; - - auto segment = edge_bundle.segment; - auto& fits = segment->fits(); - if (fits.empty()) continue; - - for (size_t i = 1; i + 1 < fits.size(); i++) { - int idx = segment_point_index_map[std::make_pair(segment, i)]; - int prev_idx = segment_point_index_map[std::make_pair(segment, i-1)]; - int next_idx = segment_point_index_map[std::make_pair(segment, i+1)]; + // 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) { + auto& edge_bundle = (*m_graph)[*e_it]; + if (!edge_bundle.segment) continue; + + auto segment = edge_bundle.segment; + auto& fits = segment->fits(); + if (fits.empty()) continue; + + for (size_t i = 1; i + 1 < fits.size(); i++) { + int idx = segment_point_index_map[std::make_pair(segment, i)]; + int prev_idx = segment_point_index_map[std::make_pair(segment, i-1)]; + int next_idx = segment_point_index_map[std::make_pair(segment, i+1)]; connected_vec[idx].push_back(prev_idx); connected_vec[idx].push_back(next_idx); @@ -5646,13 +6246,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 +6274,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; @@ -5709,10 +6312,36 @@ void TrackFitting::dQ_dx_multi_fit(double dis_end_point_ext, bool flag_dQ_dx_fit pred_data_u_2D = RU * pos_3D; pred_data_v_2D = RV * pos_3D; pred_data_w_2D = RW * pos_3D; - + + // Persist fitted 2D charge results + fill_fitted_charge_2d(map_U_charge_2D, map_V_charge_2D, map_W_charge_2D, + pred_data_u_2D, pred_data_v_2D, pred_data_w_2D, + rel_uncer_ind, rel_uncer_col, add_uncer_ind, add_uncer_col); + // 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 +6544,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 +6563,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 +6574,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 +6630,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 +6655,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 +6665,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 +6683,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 +6691,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 +6714,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 +6722,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); @@ -6406,7 +7055,12 @@ void WireCell::Clus::TrackFitting::dQ_dx_fit(double dis_end_point_ext, bool flag pred_data_u_2D = RU * pos_3D; pred_data_v_2D = RV * pos_3D; pred_data_w_2D = RW * pos_3D; - + + // Persist fitted 2D charge results + fill_fitted_charge_2d(map_U_charge_2D, map_V_charge_2D, map_W_charge_2D, + pred_data_u_2D, pred_data_v_2D, pred_data_w_2D, + rel_uncer_ind, rel_uncer_col, add_uncer_ind, add_uncer_col); + // Calculate reduced chi-squared for each 3D point reduced_chi2.resize(n_3D_pos); for (int k = 0; k < n_3D_pos; k++) { @@ -6448,6 +7102,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 +7114,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 +7131,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 +7315,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 +7485,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 +7747,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 +7830,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 417285131..8d90abac5 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 cebb39b43..a76c26b63 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 e74a3faf6..6a04d8f19 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/improvecluster_1.cxx b/clus/src/improvecluster_1.cxx index 8cc345f66..c56349d99 100644 --- a/clus/src/improvecluster_1.cxx +++ b/clus/src/improvecluster_1.cxx @@ -362,7 +362,7 @@ void ImproveCluster_1::get_activity_improved(const Cluster& cluster, std::mapconvert_time_ch_2Dpoint(time_slice, ch, apa, face, 0); + auto [x_pos, y_pos] = grouping->convert_time_ch_2Dpoint(time_slice, ch, apa, face, 0); // this should be wire index ... , so a mismatch between MicroBooNE and other detectors, need to be fixed at some points ... std::vector query_point = {static_cast(x_pos), static_cast(y_pos)}; const auto& skd = cluster.kd2d(apa, face, 0); auto ret_matches = skd.knn(1, query_point); @@ -374,7 +374,7 @@ void ImproveCluster_1::get_activity_improved(const Cluster& cluster, std::mapconvert_time_ch_2Dpoint(time_slice, ch, apa, face, 1); + auto [x_pos, y_pos] = grouping->convert_time_ch_2Dpoint(time_slice, ch, apa, face, 1); // this should be wire index ... std::vector query_point = {static_cast(x_pos), static_cast(y_pos)}; const auto& skd = cluster.kd2d(apa, face, 1); auto ret_matches = skd.knn(1, query_point); @@ -385,7 +385,7 @@ void ImproveCluster_1::get_activity_improved(const Cluster& cluster, std::mapconvert_time_ch_2Dpoint(time_slice, ch, apa, face, 2); + auto [x_pos, y_pos] = grouping->convert_time_ch_2Dpoint(time_slice, ch, apa, face, 2); // this should be wire index ... std::vector query_point = {static_cast(x_pos), static_cast(y_pos)}; const auto& skd = cluster.kd2d(apa, face, 2); auto ret_matches = skd.knn(1, query_point); @@ -403,7 +403,7 @@ void ImproveCluster_1::get_activity_improved(const Cluster& cluster, std::mapconvert_time_ch_2Dpoint(time_slice, ch, apa, face, 0); + auto [x_pos, y_pos] = grouping->convert_time_ch_2Dpoint(time_slice, ch, apa, face, 0); // this should be wire index ... std::vector query_point = {static_cast(x_pos), static_cast(y_pos)}; const auto& skd = cluster.kd2d(apa, face, 0); auto ret_matches = skd.knn(1, query_point); @@ -415,7 +415,7 @@ void ImproveCluster_1::get_activity_improved(const Cluster& cluster, std::mapconvert_time_ch_2Dpoint(time_slice, ch, apa, face, 1); + auto [x_pos, y_pos] = grouping->convert_time_ch_2Dpoint(time_slice, ch, apa, face, 1); // this should be wire index ... std::vector query_point = {static_cast(x_pos), static_cast(y_pos)}; const auto& skd = cluster.kd2d(apa, face, 1); auto ret_matches = skd.knn(1, query_point); @@ -427,7 +427,7 @@ void ImproveCluster_1::get_activity_improved(const Cluster& cluster, std::mapconvert_time_ch_2Dpoint(time_slice, ch, apa, face, 2); + auto [x_pos, y_pos] = grouping->convert_time_ch_2Dpoint(time_slice, ch, apa, face, 2); // this should be wire index ... std::vector query_point = {static_cast(x_pos), static_cast(y_pos)}; const auto& skd = cluster.kd2d(apa, face, 2); auto ret_matches = skd.knn(1, query_point); diff --git a/clus/src/make_graphs.cxx b/clus/src/make_graphs.cxx index abf93758e..1ea7208e6 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 f44c6e278..69f7663bc 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/clus/test/data/README.md b/clus/test/data/README.md new file mode 100644 index 000000000..0d29f80c2 --- /dev/null +++ b/clus/test/data/README.md @@ -0,0 +1,45 @@ +## Test Data Directory + +This directory contains configuration and input files used for testing the Wire-Cell Toolkit clustering and track fitting modules. + +--- + +### `uboone-mabc_config.json` + +**Description:** +Configuration file from the QLport directory. + +**Purpose:** +Defines detector configuration parameters for the MicroBooNE MABC (Magnetically Assisted Bubble Chamber) system. + +**Contents include:** +- Detector geometry definitions +- Electronics and readout configuration +- Processing and reconstruction parameters + +These settings are consumed by the reconstruction pipeline to ensure consistent detector modeling and data processing. + +--- + +### `init_first_segment_input.json` + +**Description:** +Debug dump of the initial first-segment input data. + +**Generation condition:** +This file is produced when the environment variable `WCT_DUMP_INIT_FIRST_SEGMENT` is set. + +**Initialization workflow captured in this file:** +1. Create a Pattern Recognition (PR) graph to represent Wire-Cell clustering structures +2. Initialize the first track segment from the main cluster using: + - Pattern-matching algorithms + - Track-fitting information +3. Register the PR graph with the track fitter to enable multi-track reconstruction +4. Execute the full multi-tracking algorithm +5. Store track-fitting results in the grouping object for downstream processing + +**Use case:** +This file is primarily intended for debugging and validation, allowing developers to: +- Inspect pattern-recognition initialization +- Verify first-segment generation +- Validate track-fitting setup within the Wire-Cell reconstruction pipeline \ No newline at end of file diff --git a/clus/test/data/init_first_segment_input.json b/clus/test/data/init_first_segment_input.json new file mode 100644 index 000000000..0fbe315c0 --- /dev/null +++ b/clus/test/data/init_first_segment_input.json @@ -0,0 +1,16215 @@ +{ + "boundary_steiner_indices" : [ 157, 11 ], + "cluster" : { + "blobs" : [ + { + "2dp0_x" : [ + 2116.1219999999998, + 2116.1219999999998, + 2116.1219999999998, + 2116.1219999999998 + ], + "2dp0_y" : [ + 1949.6019631286358, + 1949.6019484669937, + 1955.6019776423159, + 1955.6019629806738 + ], + "2dp1_x" : [ + 2116.1219999999998, + 2116.1219999999998, + 2116.1219999999998, + 2116.1219999999998 + ], + "2dp1_y" : [ + 176.88901741750715, + 179.88901935478498, + 170.88900290382719, + 173.88900484110479 + ], + "2dp2_x" : [ + 2116.1219999999998, + 2116.1219999999998, + 2116.1219999999998, + 2116.1219999999998 + ], + "2dp2_y" : [ + 2126.5000000000064, + 2129.5000000000064, + 2126.5000000000064, + 2129.5000000000064 + ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2116.1219999999998 ], + "center_y" : [ -1026.0729255248093 ], + "center_z" : [ 2128.0000000000064 ], + "charge" : [ 4356.0859375 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 2 ], + "npoints" : [ 4 ], + "slice_index_max" : [ 7048 ], + "slice_index_min" : [ 7044 ], + "u_wire_index_max" : [ 991 ], + "u_wire_index_min" : [ 988 ], + "v_wire_index_max" : [ 393 ], + "v_wire_index_min" : [ 390 ], + "w_wire_index_max" : [ 710 ], + "w_wire_index_min" : [ 708 ], + "wpid" : [ 7 ] + }, + "t" : [ 3522000.0, 3522000.0, 3522000.0, 3522000.0 ], + "ucharge_unc" : [ 1008.0, 1008.0, 1046.0, 1046.0 ], + "ucharge_val" : [ 2096.0, 2096.0, 1670.0, 1670.0 ], + "uwire_index" : [ 988, 988, 990, 990 ], + "vcharge_unc" : [ 1433.0, 0.0, 1367.0, 1492.0 ], + "vcharge_val" : [ 3460.0, 0.0, 562.0, 1926.0 ], + "vwire_index" : [ 392, 393, 390, 391 ], + "wcharge_unc" : [ 196.0, 196.0, 196.0, 196.0 ], + "wcharge_val" : [ 1182.0, 3123.0, 1182.0, 3123.0 ], + "wpid" : [ 7, 7, 7, 7 ], + "wwire_index" : [ 708, 709, 708, 709 ], + "x" : [ + 2116.1219999999998, + 2116.1219999999998, + 2116.1219999999998, + 2116.1219999999998 + ], + "x_t0cor" : [ + 2116.1219999999998, + 2116.1219999999998, + 2116.1219999999998, + 2116.1219999999998 + ], + "y" : [ + -1023.4748493988715, + -1021.7427914567201, + -1030.4030595928984, + -1028.6710016507473 + ], + "z" : [ + 2126.5000000000064, + 2129.5000000000064, + 2126.5000000000064, + 2129.5000000000064 + ] + }, + { + "2dp0_x" : [ 2118.3240000000001 ], + "2dp0_y" : [ 1934.6019341286487 ], + "2dp1_x" : [ 2118.3240000000001 ], + "2dp1_y" : [ 190.39859095028885 ], + "2dp2_x" : [ 2118.3240000000001 ], + "2dp2_y" : [ 2125.0095382110744 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2118.3240000000001 ], + "center_y" : [ -1007.0148459734767 ], + "center_z" : [ 2125.0095382110744 ], + "charge" : [ 1.0 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 1 ], + "slice_index_max" : [ 7052 ], + "slice_index_min" : [ 7048 ], + "u_wire_index_max" : [ 984 ], + "u_wire_index_min" : [ 983 ], + "v_wire_index_max" : [ 397 ], + "v_wire_index_min" : [ 396 ], + "w_wire_index_max" : [ 709 ], + "w_wire_index_min" : [ 708 ], + "wpid" : [ 7 ] + }, + "t" : [ 3524000.0 ], + "ucharge_unc" : [ 1046.0 ], + "ucharge_val" : [ 557.0 ], + "uwire_index" : [ 983 ], + "vcharge_unc" : [ 1492.0 ], + "vcharge_val" : [ 316.0 ], + "vwire_index" : [ 396 ], + "wcharge_unc" : [ 196.0 ], + "wcharge_val" : [ 5686.0 ], + "wpid" : [ 7 ], + "wwire_index" : [ 708 ], + "x" : [ 2118.3240000000001 ], + "x_t0cor" : [ 2118.3240000000001 ], + "y" : [ -1007.0148459734767 ], + "z" : [ 2125.0095382110744 ] + }, + { + "2dp0_x" : [ + 2118.3240000000001, + 2118.3240000000001, + 2118.3240000000001, + 2118.3240000000001, + 2118.3240000000001, + 2118.3240000000001, + 2118.3240000000001, + 2118.3240000000001 + ], + "2dp0_y" : [ + 1954.0924404209313, + 1951.0924331640908, + 1954.0924351013687, + 1948.0924259072503, + 1951.0924278445282, + 1945.0924186504099, + 1951.0924225249653, + 1948.0924152681248 + ], + "2dp1_x" : [ + 2118.3240000000001, + 2118.3240000000001, + 2118.3240000000001, + 2118.3240000000001, + 2118.3240000000001, + 2118.3240000000001, + 2118.3240000000001, + 2118.3240000000001 + ], + "2dp1_y" : [ + 172.3985401252047, + 175.39854738204519, + 175.3985327204033, + 178.39855463888568, + 178.39853997724367, + 181.39856189572617, + 181.39853257244181, + 184.3985398292823 + ], + "2dp2_x" : [ + 2118.3240000000001, + 2118.3240000000001, + 2118.3240000000001, + 2118.3240000000001, + 2118.3240000000001, + 2118.3240000000001, + 2118.3240000000001, + 2118.3240000000001 + ], + "2dp2_y" : [ + 2126.4999999999995, + 2126.4999999999995, + 2129.4999999999995, + 2126.4999999999995, + 2129.4999999999995, + 2126.4999999999995, + 2132.4999999999995, + 2132.4999999999995 + ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2118.3240000000001 ], + "center_y" : [ -1023.0308318776164 ], + "center_z" : [ 2128.7499999999995 ], + "charge" : [ 19668.591796875 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 1 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 2 ], + "npoints" : [ 8 ], + "slice_index_max" : [ 7052 ], + "slice_index_min" : [ 7048 ], + "u_wire_index_max" : [ 991 ], + "u_wire_index_min" : [ 987 ], + "v_wire_index_max" : [ 395 ], + "v_wire_index_min" : [ 390 ], + "w_wire_index_max" : [ 711 ], + "w_wire_index_min" : [ 708 ], + "wpid" : [ 7 ] + }, + "t" : [ + 3524000.0, + 3524000.0, + 3524000.0, + 3524000.0, + 3524000.0, + 3524000.0, + 3524000.0, + 3524000.0 + ], + "ucharge_unc" : [ 1046.0, 932.0, 1046.0, 1008.0, 932.0, 1008.0, 932.0, 1008.0 ], + "ucharge_val" : [ 4671.0, 7220.0, 4671.0, 5508.0, 7220.0, 3546.0, 7220.0, 5508.0 ], + "uwire_index" : [ 990, 989, 990, 988, 989, 987, 989, 988 ], + "vcharge_unc" : [ 1367.0, 1492.0, 1492.0, 1433.0, 1433.0, 1299.0, 1299.0, 1433.0 ], + "vcharge_val" : [ 2407.0, 7288.0, 7288.0, 9276.0, 9276.0, 3936.0, 3936.0, 1604.0 ], + "vwire_index" : [ 390, 391, 391, 392, 392, 393, 393, 394 ], + "wcharge_unc" : [ 196.0, 196.0, 196.0, 196.0, 196.0, 196.0, 195.0, 195.0 ], + "wcharge_val" : [ 5686.0, 5686.0, 10783.0, 5686.0, 10783.0, 5686.0, 3142.0, 3142.0 ], + "wpid" : [ 7, 7, 7, 7, 7, 7, 7, 7 ], + "wwire_index" : [ 708, 708, 709, 708, 709, 708, 710, 710 ], + "x" : [ + 2118.3240000000001, + 2118.3240000000001, + 2118.3240000000001, + 2118.3240000000001, + 2118.3240000000001, + 2118.3240000000001, + 2118.3240000000001, + 2118.3240000000001 + ], + "x_t0cor" : [ + 2118.3240000000001, + 2118.3240000000001, + 2118.3240000000001, + 2118.3240000000001, + 2118.3240000000001, + 2118.3240000000001, + 2118.3240000000001, + 2118.3240000000001 + ], + "y" : [ + -1028.6599986150309, + -1025.1958935180169, + -1026.9279514601681, + -1021.7317884210028, + -1023.4638463631542, + -1018.2676833239888, + -1021.7317992082916, + -1018.2676941112776 + ], + "z" : [ + 2126.4999999999995, + 2126.4999999999995, + 2129.4999999999995, + 2126.4999999999995, + 2129.4999999999995, + 2126.4999999999995, + 2132.4999999999995, + 2132.4999999999995 + ] + }, + { + "2dp0_x" : [ 2120.5259999999998, 2120.5259999999998 ], + "2dp0_y" : [ 1933.092394942611, 1936.0923968798884 ], + "2dp1_x" : [ 2120.5259999999998, 2120.5259999999998 ], + "2dp1_y" : [ 190.39859832788989, 190.39858366624776 ], + "2dp2_x" : [ 2120.5259999999998, 2120.5259999999998 ], + "2dp2_y" : [ 2123.4999999999995, 2126.4999999999995 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2120.5259999999998 ], + "center_y" : [ -1007.0093390618709 ], + "center_z" : [ 2124.9999999999995 ], + "charge" : [ 740.69696044921875 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 2 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 2 ], + "slice_index_max" : [ 7056 ], + "slice_index_min" : [ 7052 ], + "u_wire_index_max" : [ 984 ], + "u_wire_index_min" : [ 983 ], + "v_wire_index_max" : [ 397 ], + "v_wire_index_min" : [ 396 ], + "w_wire_index_max" : [ 709 ], + "w_wire_index_min" : [ 707 ], + "wpid" : [ 7 ] + }, + "t" : [ 3526000.0, 3526000.0 ], + "ucharge_unc" : [ 1046.0, 0.0 ], + "ucharge_val" : [ 1273.0, 0.0 ], + "uwire_index" : [ 983, 984 ], + "vcharge_unc" : [ 1492.0, 1492.0 ], + "vcharge_val" : [ 725.0, 725.0 ], + "vwire_index" : [ 396, 396 ], + "wcharge_unc" : [ 195.0, 196.0 ], + "wcharge_val" : [ 1087.0, 11062.0 ], + "wpid" : [ 7, 7 ], + "wwire_index" : [ 707, 708 ], + "x" : [ 2120.5259999999998, 2120.5259999999998 ], + "x_t0cor" : [ 2120.5259999999998, 2120.5259999999998 ], + "y" : [ -1006.1433100907954, -1007.8753680329465 ], + "z" : [ 2123.4999999999995, 2126.4999999999995 ] + }, + { + "2dp0_x" : [ + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998 + ], + "2dp0_y" : [ + 1940.6019560197583, + 1943.6019486149557, + 1946.6019558717962, + 1946.6019412101541, + 1949.6019777902782, + 1949.6019631286358, + 1949.6019484669937, + 1949.6019338053516, + 1952.6019703854761, + 1952.6019557238342, + 1952.6019410621918, + 1955.6019776423159, + 1955.6019629806738, + 1955.6019483190314, + 1958.6019555758719 + ], + "2dp1_x" : [ + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998 + ], + "2dp1_y" : [ + 182.88903725074942, + 182.88903193118733, + 179.88902467434684, + 182.88902661162467, + 173.88901548022955, + 176.88901741750715, + 179.88901935478498, + 182.88902129206247, + 173.88901016066688, + 176.88901209794437, + 179.88901403522209, + 170.88900290382719, + 173.88900484110479, + 176.88900677838251, + 173.88899952154202 + ], + "2dp2_x" : [ + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998 + ], + "2dp2_y" : [ + 2123.5000000000064, + 2126.5000000000064, + 2126.5000000000064, + 2129.5000000000064, + 2123.5000000000064, + 2126.5000000000064, + 2129.5000000000064, + 2132.5000000000064, + 2126.5000000000064, + 2129.5000000000064, + 2132.5000000000064, + 2126.5000000000064, + 2129.5000000000064, + 2132.5000000000064, + 2132.5000000000064 + ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2120.5259999999989 ], + "center_y" : [ -1023.4748458031087 ], + "center_z" : [ 2128.5000000000068 ], + "charge" : [ 32847.328125 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 2 ], + "npoints" : [ 15 ], + "slice_index_max" : [ 7056 ], + "slice_index_min" : [ 7052 ], + "u_wire_index_max" : [ 992 ], + "u_wire_index_min" : [ 985 ], + "v_wire_index_max" : [ 395 ], + "v_wire_index_min" : [ 390 ], + "w_wire_index_max" : [ 711 ], + "w_wire_index_min" : [ 707 ], + "wpid" : [ 7 ] + }, + "t" : [ + 3526000.0, + 3526000.0, + 3526000.0, + 3526000.0, + 3526000.0, + 3526000.0, + 3526000.0, + 3526000.0, + 3526000.0, + 3526000.0, + 3526000.0, + 3526000.0, + 3526000.0, + 3526000.0, + 3526000.0 + ], + "ucharge_unc" : [ + 932.0, + 932.0, + 1008.0, + 1008.0, + 1008.0, + 1008.0, + 1008.0, + 1008.0, + 932.0, + 932.0, + 932.0, + 1046.0, + 1046.0, + 1046.0, + 932.0 + ], + "ucharge_val" : [ + 1782.0, + 4682.0, + 6493.0, + 6493.0, + 6387.0, + 6387.0, + 6387.0, + 6387.0, + 6681.0, + 6681.0, + 6681.0, + 5951.0, + 5951.0, + 5951.0, + 936.0 + ], + "uwire_index" : [ + 985, + 986, + 987, + 987, + 988, + 988, + 988, + 988, + 989, + 989, + 989, + 990, + 990, + 990, + 991 + ], + "vcharge_unc" : [ + 1433.0, + 1433.0, + 1299.0, + 1433.0, + 1492.0, + 1433.0, + 1299.0, + 1433.0, + 1492.0, + 1433.0, + 1299.0, + 1367.0, + 1492.0, + 1433.0, + 1492.0 + ], + "vcharge_val" : [ + 4747.0, + 4747.0, + 8821.0, + 4747.0, + 9722.0, + 10289.0, + 8821.0, + 4747.0, + 9722.0, + 10289.0, + 8821.0, + 4150.0, + 9722.0, + 10289.0, + 9722.0 + ], + "vwire_index" : [ + 394, + 394, + 393, + 394, + 391, + 392, + 393, + 394, + 391, + 392, + 393, + 390, + 391, + 392, + 391 + ], + "wcharge_unc" : [ + 195.0, + 196.0, + 196.0, + 196.0, + 195.0, + 196.0, + 196.0, + 195.0, + 196.0, + 196.0, + 195.0, + 196.0, + 196.0, + 195.0, + 195.0 + ], + "wcharge_val" : [ + 1087.0, + 11062.0, + 11062.0, + 16431.0, + 1087.0, + 11062.0, + 16431.0, + 4992.0, + 11062.0, + 16431.0, + 4992.0, + 11062.0, + 16431.0, + 4992.0, + 4992.0 + ], + "wpid" : [ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 ], + "wwire_index" : [ + 707, + 708, + 708, + 709, + 707, + 708, + 709, + 710, + 708, + 709, + 710, + 708, + 709, + 710, + 710 + ], + "x" : [ + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998 + ], + "x_t0cor" : [ + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998 + ], + "y" : [ + -1014.8145920499827, + -1016.5466392048444, + -1020.0107443018584, + -1018.278686359707, + -1025.206907341023, + -1023.4748493988715, + -1021.7427914567201, + -1020.0107335145686, + -1026.9389544958854, + -1025.2068965537342, + -1023.4748386115826, + -1030.4030595928984, + -1028.6710016507473, + -1026.9389437085956, + -1030.4030488056096 + ], + "z" : [ + 2123.5000000000064, + 2126.5000000000064, + 2126.5000000000064, + 2129.5000000000064, + 2123.5000000000064, + 2126.5000000000064, + 2129.5000000000064, + 2132.5000000000064, + 2126.5000000000064, + 2129.5000000000064, + 2132.5000000000064, + 2126.5000000000064, + 2129.5000000000064, + 2132.5000000000064, + 2132.5000000000064 + ] + }, + { + "2dp0_x" : [ 2120.5259999999998, 2120.5259999999998 ], + "2dp0_y" : [ 1940.6019193191357, 1943.6019119143793 ], + "2dp1_x" : [ 2120.5259999999998, 2120.5259999999998 ], + "2dp1_y" : [ 190.39856162709543, 190.39854696549901 ], + "2dp2_x" : [ 2120.5259999999998, 2120.5259999999998 ], + "2dp2_y" : [ 2131.0095195270183, 2134.0095101849902 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2120.5259999999998 ], + "center_y" : [ -1011.3449773447436 ], + "center_z" : [ 2132.509514856004 ], + "charge" : [ 1.0 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 2 ], + "slice_index_max" : [ 7056 ], + "slice_index_min" : [ 7052 ], + "u_wire_index_max" : [ 987 ], + "u_wire_index_min" : [ 985 ], + "v_wire_index_max" : [ 397 ], + "v_wire_index_min" : [ 396 ], + "w_wire_index_max" : [ 711 ], + "w_wire_index_min" : [ 709 ], + "wpid" : [ 7 ] + }, + "t" : [ 3526000.0, 3526000.0 ], + "ucharge_unc" : [ 932.0, 932.0 ], + "ucharge_val" : [ 1782.0, 4682.0 ], + "uwire_index" : [ 985, 986 ], + "vcharge_unc" : [ 1492.0, 1492.0 ], + "vcharge_val" : [ 725.0, 725.0 ], + "vwire_index" : [ 396, 396 ], + "wcharge_unc" : [ 195.0, 0.0 ], + "wcharge_val" : [ 4992.0, 0.0 ], + "wpid" : [ 7, 7 ], + "wwire_index" : [ 710, 711 ], + "x" : [ 2120.5259999999998, 2120.5259999999998 ], + "x_t0cor" : [ 2120.5259999999998, 2120.5259999999998 ], + "y" : [ -1010.4789510704902, -1012.211003618997 ], + "z" : [ 2131.0095195270183, 2134.0095101849902 ] + }, + { + "2dp0_x" : [ + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001 + ], + "2dp0_y" : [ + 1934.6019415060782, + 1940.6019413581159, + 1943.6019632765979, + 1943.6019486149557, + 1943.6019339533136, + 1943.6019192916713, + 1946.6019558717962, + 1946.6019412101541, + 1946.6019265485118, + 1952.6019850471187, + 1952.6019410621918, + 1955.6019776423159, + 1955.6019629806738, + 1955.6019483190314, + 1958.6019555758719 + ], + "2dp1_x" : [ + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001 + ], + "2dp1_y" : [ + 188.88905176442961, + 185.88903918802703, + 179.88902999390984, + 182.88903193118733, + 185.88903386846505, + 188.88903580574265, + 179.88902467434684, + 182.88902661162467, + 185.88902854890216, + 170.88900822338906, + 179.88901403522209, + 170.88900290382719, + 173.88900484110479, + 176.88900677838251, + 173.88899952154202 + ], + "2dp2_x" : [ + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001 + ], + "2dp2_y" : [ + 2123.5000000000064, + 2126.5000000000064, + 2123.5000000000064, + 2126.5000000000064, + 2129.5000000000064, + 2132.5000000000064, + 2126.5000000000064, + 2129.5000000000064, + 2132.5000000000064, + 2123.5000000000064, + 2132.5000000000064, + 2126.5000000000064, + 2129.5000000000064, + 2132.5000000000064, + 2132.5000000000064 + ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2122.7279999999996 ], + "center_y" : [ -1020.4726213856969 ], + "center_z" : [ 2128.5000000000068 ], + "charge" : [ 32677.41796875 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 2 ], + "npoints" : [ 15 ], + "slice_index_max" : [ 7060 ], + "slice_index_min" : [ 7056 ], + "u_wire_index_max" : [ 992 ], + "u_wire_index_min" : [ 983 ], + "v_wire_index_max" : [ 398 ], + "v_wire_index_min" : [ 390 ], + "w_wire_index_max" : [ 711 ], + "w_wire_index_min" : [ 707 ], + "wpid" : [ 7 ] + }, + "t" : [ + 3528000.0, + 3528000.0, + 3528000.0, + 3528000.0, + 3528000.0, + 3528000.0, + 3528000.0, + 3528000.0, + 3528000.0, + 3528000.0, + 3528000.0, + 3528000.0, + 3528000.0, + 3528000.0, + 3528000.0 + ], + "ucharge_unc" : [ + 1046.0, + 932.0, + 932.0, + 932.0, + 932.0, + 932.0, + 1008.0, + 1008.0, + 1008.0, + 932.0, + 932.0, + 1046.0, + 1046.0, + 1046.0, + 932.0 + ], + "ucharge_val" : [ + 2449.0, + 4692.0, + 5948.0, + 5948.0, + 5948.0, + 5948.0, + 5032.0, + 5032.0, + 5032.0, + 2399.0, + 2399.0, + 4500.0, + 4500.0, + 4500.0, + 2295.0 + ], + "uwire_index" : [ + 983, + 985, + 986, + 986, + 986, + 986, + 987, + 987, + 987, + 989, + 989, + 990, + 990, + 990, + 991 + ], + "vcharge_unc" : [ + 1492.0, + 1433.0, + 1299.0, + 1433.0, + 1433.0, + 1492.0, + 1299.0, + 1433.0, + 1433.0, + 1367.0, + 1299.0, + 1367.0, + 1492.0, + 1433.0, + 1492.0 + ], + "vcharge_val" : [ + 1452.0, + 4270.0, + 7381.0, + 8654.0, + 4270.0, + 1452.0, + 7381.0, + 8654.0, + 4270.0, + 4663.0, + 7381.0, + 4663.0, + 6850.0, + 4261.0, + 6850.0 + ], + "vwire_index" : [ + 396, + 395, + 393, + 394, + 395, + 396, + 393, + 394, + 395, + 390, + 393, + 390, + 391, + 392, + 391 + ], + "wcharge_unc" : [ + 195.0, + 196.0, + 195.0, + 196.0, + 196.0, + 195.0, + 196.0, + 196.0, + 195.0, + 195.0, + 195.0, + 196.0, + 196.0, + 195.0, + 195.0 + ], + "wcharge_val" : [ + 2815.0, + 12375.0, + 2815.0, + 12375.0, + 13266.0, + 4193.0, + 12375.0, + 13266.0, + 4193.0, + 2815.0, + 4193.0, + 12375.0, + 13266.0, + 4193.0, + 4193.0 + ], + "wpid" : [ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 ], + "wwire_index" : [ + 707, + 708, + 707, + 708, + 709, + 710, + 708, + 709, + 710, + 707, + 710, + 708, + 709, + 710, + 710 + ], + "x" : [ + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001 + ], + "x_t0cor" : [ + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001 + ], + "y" : [ + -1007.8863818559556, + -1013.0825341078313, + -1018.2786971469958, + -1016.5466392048444, + -1014.814581262693, + -1013.0825233205416, + -1020.0107443018584, + -1018.278686359707, + -1016.5466284175556, + -1028.671012438037, + -1023.4748386115826, + -1030.4030595928984, + -1028.6710016507473, + -1026.9389437085956, + -1030.4030488056096 + ], + "z" : [ + 2123.5000000000064, + 2126.5000000000064, + 2123.5000000000064, + 2126.5000000000064, + 2129.5000000000064, + 2132.5000000000064, + 2126.5000000000064, + 2129.5000000000064, + 2132.5000000000064, + 2123.5000000000064, + 2132.5000000000064, + 2126.5000000000064, + 2129.5000000000064, + 2132.5000000000064, + 2132.5000000000064 + ] + }, + { + "2dp0_x" : [ 2124.9299999999998, 2124.9299999999998, 2124.9299999999998 ], + "2dp0_y" : [ 1943.6019852223624, 1946.6019924792026, 1946.601977817606 ], + "2dp1_x" : [ 2124.9299999999998, 2124.9299999999998, 2124.9299999999998 ], + "2dp1_y" : [ 175.39858398928186, 172.39857673244171, 175.3985693276851 ], + "2dp2_x" : [ 2124.9299999999998, 2124.9299999999998, 2124.9299999999998 ], + "2dp2_y" : [ 2119.0095568951306, 2119.0095568951306, 2122.0095475531025 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2124.9299999999998 ], + "center_y" : [ -1022.6033189100376 ], + "center_z" : [ 2120.0095537811212 ], + "charge" : [ 1969.640869140625 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 3 ], + "slice_index_max" : [ 7064 ], + "slice_index_min" : [ 7060 ], + "u_wire_index_max" : [ 988 ], + "u_wire_index_min" : [ 986 ], + "v_wire_index_max" : [ 392 ], + "v_wire_index_min" : [ 390 ], + "w_wire_index_max" : [ 708 ], + "w_wire_index_min" : [ 706 ], + "wpid" : [ 7 ] + }, + "t" : [ 3530000.0, 3530000.0, 3530000.0 ], + "ucharge_unc" : [ 932.0, 1008.0, 1008.0 ], + "ucharge_val" : [ 3033.0, 1283.0, 1283.0 ], + "uwire_index" : [ 986, 987, 987 ], + "vcharge_unc" : [ 1492.0, 1367.0, 1492.0 ], + "vcharge_val" : [ 3439.0, 3188.0, 3439.0 ], + "vwire_index" : [ 391, 390, 391 ], + "wcharge_unc" : [ 196.0, 196.0, 195.0 ], + "wcharge_val" : [ 488.0, 488.0, 4821.0 ], + "wpid" : [ 7, 7, 7 ], + "wwire_index" : [ 706, 706, 707 ], + "x" : [ 2124.9299999999998, 2124.9299999999998, 2124.9299999999998 ], + "x_t0cor" : [ 2124.9299999999998, 2124.9299999999998, 2124.9299999999998 ], + "y" : [ -1020.8712663615308, -1024.3353714585444, -1022.6033189100376 ], + "z" : [ 2119.0095568951306, 2119.0095568951306, 2122.0095475531025 ] + }, + { + "2dp0_x" : [ + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998 + ], + "2dp0_y" : [ + 1925.6019416813219, + 1934.6019487902456, + 1934.6019341286487, + 1934.6019194670521, + 1937.6019560470857, + 1937.601941385489, + 1937.6019267238923, + 1940.601948642329, + 1940.6019339807324, + 1943.6019558991691, + 1943.6019119143793, + 1946.6019484944125 + ], + "2dp1_x" : [ + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998 + ], + "2dp1_y" : [ + 193.39862753032219, + 187.39859835504535, + 190.39859095028885, + 193.39858354553223, + 184.39859109820532, + 187.3985836934487, + 190.39857628869208, + 184.39857643660866, + 187.39856903185205, + 181.39856917976863, + 190.39854696549901, + 181.39855451817198 + ], + "2dp2_x" : [ + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998 + ], + "2dp2_y" : [ + 2119.0095568951306, + 2122.0095475531025, + 2125.0095382110744, + 2128.0095288690463, + 2122.0095475531025, + 2125.0095382110744, + 2128.0095288690463, + 2125.0095382110744, + 2128.0095288690463, + 2125.0095382110744, + 2134.0095101849902, + 2128.0095288690463 + ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2124.9299999999998 ], + "center_y" : [ -1010.6232887828659 ], + "center_z" : [ 2125.7595358755675 ], + "charge" : [ 20269.4375 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 12 ], + "slice_index_max" : [ 7064 ], + "slice_index_min" : [ 7060 ], + "u_wire_index_max" : [ 988 ], + "u_wire_index_min" : [ 980 ], + "v_wire_index_max" : [ 398 ], + "v_wire_index_min" : [ 393 ], + "w_wire_index_max" : [ 711 ], + "w_wire_index_min" : [ 706 ], + "wpid" : [ 7 ] + }, + "t" : [ + 3530000.0, + 3530000.0, + 3530000.0, + 3530000.0, + 3530000.0, + 3530000.0, + 3530000.0, + 3530000.0, + 3530000.0, + 3530000.0, + 3530000.0, + 3530000.0 + ], + "ucharge_unc" : [ + 1046.0, + 1046.0, + 1046.0, + 1046.0, + 1008.0, + 1008.0, + 1008.0, + 932.0, + 932.0, + 932.0, + 932.0, + 1008.0 + ], + "ucharge_val" : [ + 763.0, + 5371.0, + 5371.0, + 5371.0, + 5737.0, + 5737.0, + 5737.0, + 4067.0, + 4067.0, + 3033.0, + 3033.0, + 1283.0 + ], + "uwire_index" : [ 980, 983, 983, 983, 984, 984, 984, 985, 985, 986, 986, 987 ], + "vcharge_unc" : [ + 1552.0, + 1433.0, + 1492.0, + 1552.0, + 1433.0, + 1433.0, + 1492.0, + 1433.0, + 1433.0, + 1299.0, + 1492.0, + 1299.0 + ], + "vcharge_val" : [ + 1990.0, + 7345.0, + 5056.0, + 1990.0, + 7386.0, + 7345.0, + 5056.0, + 7386.0, + 7345.0, + 2379.0, + 5056.0, + 2379.0 + ], + "vwire_index" : [ 397, 395, 396, 397, 394, 395, 396, 394, 395, 393, 396, 393 ], + "wcharge_unc" : [ + 196.0, + 195.0, + 196.0, + 196.0, + 195.0, + 196.0, + 196.0, + 196.0, + 196.0, + 196.0, + 0.0, + 196.0 + ], + "wcharge_val" : [ + 488.0, + 4821.0, + 11215.0, + 7113.0, + 4821.0, + 11215.0, + 7113.0, + 11215.0, + 7113.0, + 11215.0, + 0.0, + 7113.0 + ], + "wpid" : [ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 ], + "wwire_index" : [ 706, 707, 708, 709, 707, 708, 709, 708, 709, 708, 711, 709 ], + "x" : [ + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998 + ], + "x_t0cor" : [ + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998 + ], + "y" : [ + -1000.0866357794496, + -1008.7468985219834, + -1007.0148459734767, + -1005.2827934249699, + -1012.211003618997, + -1010.4789510704902, + -1008.7468985219834, + -1013.9430561675038, + -1012.211003618997, + -1017.4071612645173, + -1012.211003618997, + -1019.1392138130241 + ], + "z" : [ + 2119.0095568951306, + 2122.0095475531025, + 2125.0095382110744, + 2128.0095288690463, + 2122.0095475531025, + 2125.0095382110744, + 2128.0095288690463, + 2125.0095382110744, + 2128.0095288690463, + 2125.0095382110744, + 2134.0095101849902, + 2128.0095288690463 + ] + }, + { + "2dp0_x" : [ 2124.9299999999998, 2124.9299999999998, 2124.9299999999998 ], + "2dp0_y" : [ 1954.0924404209309, 1960.0924442954868, 1957.0924370386463 ], + "2dp1_x" : [ 2124.9299999999998, 2124.9299999999998, 2124.9299999999998 ], + "2dp1_y" : [ 172.39854012520516, 172.39851080192034, 175.39851805876083 ], + "2dp2_x" : [ 2124.9299999999998, 2124.9299999999998, 2124.9299999999998 ], + "2dp2_y" : [ 2126.4999999999995, 2132.4999999999995, 2132.4999999999995 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2124.9299999999998 ], + "center_y" : [ -1029.8147075055613 ], + "center_z" : [ 2130.4999999999995 ], + "charge" : [ 3899.175048828125 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 2 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 3 ], + "slice_index_max" : [ 7064 ], + "slice_index_min" : [ 7060 ], + "u_wire_index_max" : [ 992 ], + "u_wire_index_min" : [ 990 ], + "v_wire_index_max" : [ 392 ], + "v_wire_index_min" : [ 390 ], + "w_wire_index_max" : [ 711 ], + "w_wire_index_min" : [ 708 ], + "wpid" : [ 7 ] + }, + "t" : [ 3530000.0, 3530000.0, 3530000.0 ], + "ucharge_unc" : [ 1046.0, 0.0, 932.0 ], + "ucharge_val" : [ 2409.0, 0.0, 2040.0 ], + "uwire_index" : [ 990, 992, 991 ], + "vcharge_unc" : [ 1367.0, 1367.0, 1492.0 ], + "vcharge_val" : [ 3188.0, 3188.0, 3439.0 ], + "vwire_index" : [ 390, 390, 391 ], + "wcharge_unc" : [ 196.0, 195.0, 195.0 ], + "wcharge_val" : [ 11215.0, 2422.0, 2422.0 ], + "wpid" : [ 7, 7, 7 ], + "wwire_index" : [ 708, 710, 710 ], + "x" : [ 2124.9299999999998, 2124.9299999999998, 2124.9299999999998 ], + "x_t0cor" : [ 2124.9299999999998, 2124.9299999999998, 2124.9299999999998 ], + "y" : [ -1028.6599986150304, -1032.1241144993337, -1028.6600094023197 ], + "z" : [ 2126.4999999999995, 2132.4999999999995, 2132.4999999999995 ] + }, + { + "2dp0_x" : [ 2127.1320000000001, 2127.1320000000001, 2127.1320000000001 ], + "2dp0_y" : [ 1940.6019926271188, 1946.6019924792026, 1946.601977817606 ], + "2dp1_x" : [ 2127.1320000000001, 2127.1320000000001, 2127.1320000000001 ], + "2dp1_y" : [ 175.39859865087828, 172.39857673244171, 175.3985693276851 ], + "2dp2_x" : [ 2127.1320000000001, 2127.1320000000001, 2127.1320000000001 ], + "2dp2_y" : [ 2116.0095662371587, 2119.0095568951306, 2122.0095475531025 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2127.1320000000001 ], + "center_y" : [ -1022.0259680605353 ], + "center_z" : [ 2119.0095568951306 ], + "charge" : [ 261.18280029296875 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 3 ], + "slice_index_max" : [ 7068 ], + "slice_index_min" : [ 7064 ], + "u_wire_index_max" : [ 988 ], + "u_wire_index_min" : [ 985 ], + "v_wire_index_max" : [ 392 ], + "v_wire_index_min" : [ 390 ], + "w_wire_index_max" : [ 708 ], + "w_wire_index_min" : [ 705 ], + "wpid" : [ 7 ] + }, + "t" : [ 3532000.0, 3532000.0, 3532000.0 ], + "ucharge_unc" : [ 932.0, 1008.0, 1008.0 ], + "ucharge_val" : [ 1364.0, 383.0, 383.0 ], + "uwire_index" : [ 985, 987, 987 ], + "vcharge_unc" : [ 1492.0, 1367.0, 1492.0 ], + "vcharge_val" : [ 1403.0, 1331.0, 1403.0 ], + "vwire_index" : [ 391, 390, 391 ], + "wcharge_unc" : [ 195.0, 196.0, 195.0 ], + "wcharge_val" : [ 127.0, 1169.0, 7594.0 ], + "wpid" : [ 7, 7, 7 ], + "wwire_index" : [ 705, 706, 707 ], + "x" : [ 2127.1320000000001, 2127.1320000000001, 2127.1320000000001 ], + "x_t0cor" : [ 2127.1320000000001, 2127.1320000000001, 2127.1320000000001 ], + "y" : [ -1019.1392138130241, -1024.3353714585444, -1022.6033189100376 ], + "z" : [ 2116.0095662371587, 2119.0095568951306, 2122.0095475531025 ] + }, + { + "2dp0_x" : [ + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001 + ], + "2dp0_y" : [ + 1925.6019416813219, + 1931.6019415334054, + 1931.6019268718087, + 1934.6019634518423, + 1934.6019487902456, + 1934.6019341286487, + 1934.6019194670521, + 1937.601941385489, + 1943.6019412375724, + 1946.6019338328158 + ], + "2dp1_x" : [ + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001 + ], + "2dp1_y" : [ + 193.39862753032219, + 190.3986056118855, + 193.39859820712888, + 184.39860575980197, + 187.39859835504535, + 190.39859095028885, + 193.39858354553223, + 187.3985836934487, + 184.39856177501201, + 184.39854711341536 + ], + "2dp2_x" : [ + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001 + ], + "2dp2_y" : [ + 2119.0095568951306, + 2122.0095475531025, + 2125.0095382110744, + 2119.0095568951306, + 2122.0095475531025, + 2125.0095382110744, + 2128.0095288690463, + 2125.0095382110744, + 2128.0095288690463, + 2131.0095195270183 + ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2127.1320000000005 ], + "center_y" : [ -1008.400488012282 ], + "center_z" : [ 2124.4095400794799 ], + "charge" : [ 20518.677734375 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 10 ], + "slice_index_max" : [ 7068 ], + "slice_index_min" : [ 7064 ], + "u_wire_index_max" : [ 988 ], + "u_wire_index_min" : [ 980 ], + "v_wire_index_max" : [ 398 ], + "v_wire_index_min" : [ 394 ], + "w_wire_index_max" : [ 711 ], + "w_wire_index_min" : [ 705 ], + "wpid" : [ 7 ] + }, + "t" : [ + 3532000.0, + 3532000.0, + 3532000.0, + 3532000.0, + 3532000.0, + 3532000.0, + 3532000.0, + 3532000.0, + 3532000.0, + 3532000.0 + ], + "ucharge_unc" : [ + 1046.0, + 932.0, + 932.0, + 1046.0, + 1046.0, + 1046.0, + 1046.0, + 1008.0, + 932.0, + 1008.0 + ], + "ucharge_val" : [ + 1183.0, + 4494.0, + 4494.0, + 6965.0, + 6965.0, + 6965.0, + 6965.0, + 4282.0, + 1001.0, + 383.0 + ], + "uwire_index" : [ 980, 982, 982, 983, 983, 983, 983, 984, 986, 987 ], + "vcharge_unc" : [ + 1552.0, + 1492.0, + 1552.0, + 1433.0, + 1433.0, + 1492.0, + 1552.0, + 1433.0, + 1433.0, + 1433.0 + ], + "vcharge_val" : [ + 5462.0, + 8499.0, + 5462.0, + 2746.0, + 6022.0, + 8499.0, + 5462.0, + 6022.0, + 2746.0, + 2746.0 + ], + "vwire_index" : [ 397, 396, 397, 394, 395, 396, 397, 395, 394, 394 ], + "wcharge_unc" : [ 196.0, 195.0, 196.0, 196.0, 195.0, 196.0, 196.0, 196.0, 196.0, 195.0 ], + "wcharge_val" : [ + 1169.0, + 7594.0, + 9272.0, + 1169.0, + 7594.0, + 9272.0, + 2983.0, + 9272.0, + 2983.0, + 1199.0 + ], + "wpid" : [ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 ], + "wwire_index" : [ 706, 707, 708, 706, 707, 708, 709, 708, 709, 710 ], + "x" : [ + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001 + ], + "x_t0cor" : [ + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001 + ], + "y" : [ + -1000.0866357794496, + -1005.2827934249699, + -1003.5507408764631, + -1010.4789510704902, + -1008.7468985219834, + -1007.0148459734767, + -1005.2827934249699, + -1010.4789510704902, + -1015.6751087160105, + -1017.4071612645173 + ], + "z" : [ + 2119.0095568951306, + 2122.0095475531025, + 2125.0095382110744, + 2119.0095568951306, + 2122.0095475531025, + 2125.0095382110744, + 2128.0095288690463, + 2125.0095382110744, + 2128.0095288690463, + 2131.0095195270183 + ] + }, + { + "2dp0_x" : [ 2127.1320000000001, 2127.1320000000001 ], + "2dp0_y" : [ 1925.6019123581286, 1934.6018901438592 ], + "2dp1_x" : [ 2127.1320000000001, 2127.1320000000001 ], + "2dp1_y" : [ 199.39861272080896, 199.39856873601923 ], + "2dp2_x" : [ 2127.1320000000001, 2127.1320000000001 ], + "2dp2_y" : [ 2125.0095382110744, 2134.0095101849902 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2127.1320000000001 ], + "center_y" : [ -999.22060950519619 ], + "center_z" : [ 2129.5095241980325 ], + "charge" : [ 1.0 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 2 ], + "slice_index_max" : [ 7068 ], + "slice_index_min" : [ 7064 ], + "u_wire_index_max" : [ 984 ], + "u_wire_index_min" : [ 980 ], + "v_wire_index_max" : [ 400 ], + "v_wire_index_min" : [ 399 ], + "w_wire_index_max" : [ 711 ], + "w_wire_index_min" : [ 707 ], + "wpid" : [ 7 ] + }, + "t" : [ 3532000.0, 3532000.0 ], + "ucharge_unc" : [ 1046.0, 1046.0 ], + "ucharge_val" : [ 1183.0, 6965.0 ], + "uwire_index" : [ 980, 983 ], + "vcharge_unc" : [ 1552.0, 1552.0 ], + "vcharge_val" : [ 436.0, 436.0 ], + "vwire_index" : [ 399, 399 ], + "wcharge_unc" : [ 196.0, 0.0 ], + "wcharge_val" : [ 9272.0, 0.0 ], + "wpid" : [ 7, 7 ], + "wwire_index" : [ 708, 711 ], + "x" : [ 2127.1320000000001, 2127.1320000000001 ], + "x_t0cor" : [ 2127.1320000000001, 2127.1320000000001 ], + "y" : [ -996.62253068243604, -1001.8186883279564 ], + "z" : [ 2125.0095382110744, 2134.0095101849902 ] + }, + { + "2dp0_x" : [ 2127.1320000000001, 2127.1320000000001, 2127.1320000000001 ], + "2dp0_y" : [ 1954.0924404209309, 1960.0924442954868, 1957.0924370386463 ], + "2dp1_x" : [ 2127.1320000000001, 2127.1320000000001, 2127.1320000000001 ], + "2dp1_y" : [ 172.39854012520516, 172.39851080192034, 175.39851805876083 ], + "2dp2_x" : [ 2127.1320000000001, 2127.1320000000001, 2127.1320000000001 ], + "2dp2_y" : [ 2126.4999999999995, 2132.4999999999995, 2132.4999999999995 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2127.1320000000001 ], + "center_y" : [ -1029.8147075055613 ], + "center_z" : [ 2130.4999999999995 ], + "charge" : [ 1659.468505859375 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 2 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 3 ], + "slice_index_max" : [ 7068 ], + "slice_index_min" : [ 7064 ], + "u_wire_index_max" : [ 992 ], + "u_wire_index_min" : [ 990 ], + "v_wire_index_max" : [ 392 ], + "v_wire_index_min" : [ 390 ], + "w_wire_index_max" : [ 711 ], + "w_wire_index_min" : [ 708 ], + "wpid" : [ 7 ] + }, + "t" : [ 3532000.0, 3532000.0, 3532000.0 ], + "ucharge_unc" : [ 1046.0, 0.0, 932.0 ], + "ucharge_val" : [ 872.0, 0.0, 1502.0 ], + "uwire_index" : [ 990, 992, 991 ], + "vcharge_unc" : [ 1367.0, 1367.0, 1492.0 ], + "vcharge_val" : [ 1331.0, 1331.0, 1403.0 ], + "vwire_index" : [ 390, 390, 391 ], + "wcharge_unc" : [ 196.0, 195.0, 195.0 ], + "wcharge_val" : [ 9272.0, 1199.0, 1199.0 ], + "wpid" : [ 7, 7, 7 ], + "wwire_index" : [ 708, 710, 710 ], + "x" : [ 2127.1320000000001, 2127.1320000000001, 2127.1320000000001 ], + "x_t0cor" : [ 2127.1320000000001, 2127.1320000000001, 2127.1320000000001 ], + "y" : [ -1028.6599986150304, -1032.1241144993337, -1028.6600094023197 ], + "z" : [ 2126.4999999999995, 2132.4999999999995, 2132.4999999999995 ] + }, + { + "2dp0_x" : [ 2129.3339999999998, 2129.3339999999998 ], + "2dp0_y" : [ 1919.6019271676419, 1919.6019125060452 ], + "2dp1_x" : [ 2129.3339999999998, 2129.3339999999998 ], + "2dp1_y" : [ 199.39864204400249, 202.39863463924587 ], + "2dp2_x" : [ 2129.3339999999998, 2129.3339999999998 ], + "2dp2_y" : [ 2119.0095568951306, 2122.0095475531025 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2129.3339999999998 ], + "center_y" : [ -992.29239931116888 ], + "center_z" : [ 2120.5095522241163 ], + "charge" : [ 563.97552490234375 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 1 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 0 ], + "npoints" : [ 2 ], + "slice_index_max" : [ 7072 ], + "slice_index_min" : [ 7068 ], + "u_wire_index_max" : [ 979 ], + "u_wire_index_min" : [ 978 ], + "v_wire_index_max" : [ 401 ], + "v_wire_index_min" : [ 399 ], + "w_wire_index_max" : [ 708 ], + "w_wire_index_min" : [ 706 ], + "wpid" : [ 7 ] + }, + "t" : [ 3534000.0, 3534000.0 ], + "ucharge_unc" : [ 971.0, 971.0 ], + "ucharge_val" : [ 712.0, 712.0 ], + "uwire_index" : [ 978, 978 ], + "vcharge_unc" : [ 1552.0, 1367.0 ], + "vcharge_val" : [ 1240.0, 792.0 ], + "vwire_index" : [ 399, 400 ], + "wcharge_unc" : [ 196.0, 195.0 ], + "wcharge_val" : [ 2568.0, 10389.0 ], + "wpid" : [ 7, 7 ], + "wwire_index" : [ 706, 707 ], + "x" : [ 2129.3339999999998, 2129.3339999999998 ], + "x_t0cor" : [ 2129.3339999999998, 2129.3339999999998 ], + "y" : [ -993.15842558542226, -991.42637303691549 ], + "z" : [ 2119.0095568951306, 2122.0095475531025 ] + }, + { + "2dp0_x" : [ + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998 + ], + "2dp0_y" : [ + 1925.6019343971998, + 1925.6018904122732, + 1931.6019342492377, + 1934.6019561677203, + 1934.6019415060782, + 1934.6019268444359, + 1934.6019121827937, + 1934.6018975211514, + 1940.601926696474, + 1940.6019120348315 + ], + "2dp1_x" : [ + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998 + ], + "2dp1_y" : [ + 194.88907159767268, + 203.88907740950549, + 191.8890590212701, + 185.88904982715201, + 188.88905176442961, + 191.8890537017071, + 194.88905563898493, + 197.88905757626242, + 188.88904112530474, + 191.88904306258235 + ], + "2dp2_x" : [ + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998 + ], + "2dp2_y" : [ + 2120.5000000000064, + 2129.5000000000064, + 2123.5000000000064, + 2120.5000000000064, + 2123.5000000000064, + 2126.5000000000064, + 2129.5000000000064, + 2132.5000000000064, + 2129.5000000000064, + 2132.5000000000064 + ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2129.3339999999994 ], + "center_y" : [ -1004.9418865904848 ], + "center_z" : [ 2126.800000000007 ], + "charge" : [ 20848.095703125 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 2 ], + "npoints" : [ 10 ], + "slice_index_max" : [ 7072 ], + "slice_index_min" : [ 7068 ], + "u_wire_index_max" : [ 986 ], + "u_wire_index_min" : [ 980 ], + "v_wire_index_max" : [ 401 ], + "v_wire_index_min" : [ 395 ], + "w_wire_index_max" : [ 711 ], + "w_wire_index_min" : [ 706 ], + "wpid" : [ 7 ] + }, + "t" : [ + 3534000.0, + 3534000.0, + 3534000.0, + 3534000.0, + 3534000.0, + 3534000.0, + 3534000.0, + 3534000.0, + 3534000.0, + 3534000.0 + ], + "ucharge_unc" : [ + 1046.0, + 1046.0, + 932.0, + 1046.0, + 1046.0, + 1046.0, + 1046.0, + 1046.0, + 932.0, + 932.0 + ], + "ucharge_val" : [ + 3823.0, + 3823.0, + 6533.0, + 4625.0, + 4625.0, + 4625.0, + 4625.0, + 4625.0, + 263.0, + 263.0 + ], + "uwire_index" : [ 980, 980, 982, 983, 983, 983, 983, 983, 985, 985 ], + "vcharge_unc" : [ + 1433.0, + 0.0, + 1552.0, + 1433.0, + 1492.0, + 1552.0, + 1433.0, + 1552.0, + 1492.0, + 1552.0 + ], + "vcharge_val" : [ + 3310.0, + 0.0, + 9265.0, + 2276.0, + 6727.0, + 9265.0, + 3310.0, + 1240.0, + 6727.0, + 9265.0 + ], + "vwire_index" : [ 398, 401, 397, 395, 396, 397, 398, 399, 396, 397 ], + "wcharge_unc" : [ 196.0, 196.0, 195.0, 196.0, 195.0, 196.0, 196.0, 195.0, 196.0, 195.0 ], + "wcharge_val" : [ + 2568.0, + 1240.0, + 10389.0, + 2568.0, + 10389.0, + 6599.0, + 1240.0, + 643.0, + 1240.0, + 643.0 + ], + "wpid" : [ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 ], + "wwire_index" : [ 706, 709, 707, 706, 707, 708, 709, 710, 709, 710 ], + "x" : [ + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998 + ], + "x_t0cor" : [ + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998 + ], + "y" : [ + -999.22612450706595, + -994.02995068061171, + -1004.4222767589416, + -1009.618439798107, + -1007.8863818559556, + -1006.1543239138042, + -1004.4222659716528, + -1002.6902080295014, + -1011.3504761656799, + -1009.6184182235285 + ], + "z" : [ + 2120.5000000000064, + 2129.5000000000064, + 2123.5000000000064, + 2120.5000000000064, + 2123.5000000000064, + 2126.5000000000064, + 2129.5000000000064, + 2132.5000000000064, + 2129.5000000000064, + 2132.5000000000064 + ] + }, + { + "2dp0_x" : [ + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001 + ], + "2dp0_y" : [ + 1919.6019271676419, + 1919.6019125060452, + 1925.6019416813219, + 1925.6019270197253, + 1925.6019123581286, + 1928.601948938162, + 1928.6019342765653, + 1928.6019196149687, + 1928.601904953372, + 1928.6018902917754, + 1934.6019341286487, + 1934.6018901438592 + ], + "2dp1_x" : [ + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001 + ], + "2dp1_y" : [ + 199.39864204400237, + 202.39863463924576, + 193.39862753032219, + 196.39862012556557, + 199.39861272080896, + 190.39862027348215, + 193.39861286872554, + 196.39860546396892, + 199.3985980592123, + 202.39859065445569, + 190.39859095028885, + 199.39856873601923 + ], + "2dp2_x" : [ + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001 + ], + "2dp2_y" : [ + 2119.0095568951306, + 2122.0095475531025, + 2119.0095568951306, + 2122.0095475531025, + 2125.0095382110744, + 2119.0095568951306, + 2122.0095475531025, + 2125.0095382110744, + 2128.0095288690463, + 2131.0095195270183, + 2125.0095382110744, + 2134.0095101849902 + ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2131.5360000000001 ], + "center_y" : [ -999.07627179282065 ], + "center_z" : [ 2124.2595405465813 ], + "charge" : [ 20568.09765625 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 12 ], + "slice_index_max" : [ 7076 ], + "slice_index_min" : [ 7072 ], + "u_wire_index_max" : [ 984 ], + "u_wire_index_min" : [ 978 ], + "v_wire_index_max" : [ 401 ], + "v_wire_index_min" : [ 396 ], + "w_wire_index_max" : [ 711 ], + "w_wire_index_min" : [ 706 ], + "wpid" : [ 7 ] + }, + "t" : [ + 3536000.0, + 3536000.0, + 3536000.0, + 3536000.0, + 3536000.0, + 3536000.0, + 3536000.0, + 3536000.0, + 3536000.0, + 3536000.0, + 3536000.0, + 3536000.0 + ], + "ucharge_unc" : [ + 971.0, + 971.0, + 1046.0, + 1046.0, + 1046.0, + 898.0, + 898.0, + 898.0, + 898.0, + 898.0, + 1046.0, + 1046.0 + ], + "ucharge_val" : [ + 1994.0, + 1994.0, + 6334.0, + 6334.0, + 6334.0, + 5206.0, + 5206.0, + 5206.0, + 5206.0, + 5206.0, + 1271.0, + 1271.0 + ], + "uwire_index" : [ 978, 978, 980, 980, 980, 981, 981, 981, 981, 981, 983, 983 ], + "vcharge_unc" : [ + 1552.0, + 1367.0, + 1552.0, + 1433.0, + 1552.0, + 1492.0, + 1552.0, + 1433.0, + 1552.0, + 1367.0, + 1492.0, + 1552.0 + ], + "vcharge_val" : [ + 4241.0, + 1767.0, + 7599.0, + 7138.0, + 4241.0, + 2029.0, + 7599.0, + 7138.0, + 4241.0, + 1767.0, + 2029.0, + 4241.0 + ], + "vwire_index" : [ 399, 400, 397, 398, 399, 396, 397, 398, 399, 400, 396, 399 ], + "wcharge_unc" : [ + 196.0, + 195.0, + 196.0, + 195.0, + 196.0, + 196.0, + 195.0, + 196.0, + 196.0, + 195.0, + 196.0, + 0.0 + ], + "wcharge_val" : [ + 5364.0, + 11075.0, + 5364.0, + 11075.0, + 4067.0, + 5364.0, + 11075.0, + 4067.0, + 800.0, + 216.0, + 4067.0, + 0.0 + ], + "wpid" : [ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 ], + "wwire_index" : [ 706, 707, 706, 707, 708, 706, 707, 708, 709, 710, 708, 711 ], + "x" : [ + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001 + ], + "x_t0cor" : [ + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001 + ], + "y" : [ + -993.15842558542249, + -991.42637303691572, + -1000.0866357794496, + -998.35458323094281, + -996.62253068243604, + -1003.5507408764631, + -1001.8186883279564, + -1000.0866357794496, + -998.35458323094281, + -996.62253068243604, + -1007.0148459734767, + -1001.8186883279564 + ], + "z" : [ + 2119.0095568951306, + 2122.0095475531025, + 2119.0095568951306, + 2122.0095475531025, + 2125.0095382110744, + 2119.0095568951306, + 2122.0095475531025, + 2125.0095382110744, + 2128.0095288690463, + 2131.0095195270183, + 2125.0095382110744, + 2134.0095101849902 + ] + }, + { + "2dp0_x" : [ + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998 + ], + "2dp0_y" : [ + 1916.6019199108018, + 1919.6019271676419, + 1919.6019125060452, + 1922.6019344244819, + 1922.6019197628852, + 1925.6019416813219, + 1925.6019270197253, + 1925.601897696532, + 1928.6019342765653, + 1928.6018902917754 + ], + "2dp1_x" : [ + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998 + ], + "2dp1_y" : [ + 202.39864930084241, + 199.39864204400237, + 202.39863463924576, + 196.39863478716222, + 199.39862738240561, + 193.39862753032219, + 196.39862012556557, + 202.39860531605234, + 193.39861286872554, + 202.39859065445569 + ], + "2dp2_x" : [ + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998 + ], + "2dp2_y" : [ + 2119.0095568951306, + 2119.0095568951306, + 2122.0095475531025, + 2119.0095568951306, + 2122.0095475531025, + 2119.0095568951306, + 2122.0095475531025, + 2128.0095288690463, + 2122.0095475531025, + 2131.0095195270183 + ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2133.7380000000003 ], + "center_y" : [ -995.75650440818265 ], + "center_z" : [ 2122.3095466188997 ], + "charge" : [ 19521.806640625 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 10 ], + "slice_index_max" : [ 7080 ], + "slice_index_min" : [ 7076 ], + "u_wire_index_max" : [ 982 ], + "u_wire_index_min" : [ 977 ], + "v_wire_index_max" : [ 401 ], + "v_wire_index_min" : [ 397 ], + "w_wire_index_max" : [ 710 ], + "w_wire_index_min" : [ 705 ], + "wpid" : [ 7 ] + }, + "t" : [ + 3538000.0, + 3538000.0, + 3538000.0, + 3538000.0, + 3538000.0, + 3538000.0, + 3538000.0, + 3538000.0, + 3538000.0, + 3538000.0 + ], + "ucharge_unc" : [ + 1008.0, + 971.0, + 971.0, + 932.0, + 932.0, + 1046.0, + 1046.0, + 1046.0, + 898.0, + 898.0 + ], + "ucharge_val" : [ + 2710.0, + 4774.0, + 4774.0, + 4953.0, + 4953.0, + 5244.0, + 5244.0, + 5244.0, + 1625.0, + 1625.0 + ], + "uwire_index" : [ 977, 978, 978, 979, 979, 980, 980, 980, 981, 981 ], + "vcharge_unc" : [ + 1367.0, + 1552.0, + 1367.0, + 1433.0, + 1552.0, + 1552.0, + 1433.0, + 1367.0, + 1552.0, + 1367.0 + ], + "vcharge_val" : [ + 5014.0, + 7814.0, + 5014.0, + 5821.0, + 7814.0, + 2747.0, + 5821.0, + 5014.0, + 2747.0, + 5014.0 + ], + "vwire_index" : [ 400, 399, 400, 398, 399, 397, 398, 400, 397, 400 ], + "wcharge_unc" : [ 196.0, 196.0, 195.0, 196.0, 195.0, 196.0, 195.0, 196.0, 195.0, 0.0 ], + "wcharge_val" : [ + 7956.0, + 7956.0, + 9287.0, + 7956.0, + 9287.0, + 7956.0, + 9287.0, + 338.0, + 9287.0, + 0.0 + ], + "wpid" : [ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 ], + "wwire_index" : [ 706, 706, 707, 706, 707, 706, 707, 709, 707, 710 ], + "x" : [ + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998 + ], + "x_t0cor" : [ + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998 + ], + "y" : [ + -989.69432048840895, + -993.15842558542249, + -991.42637303691572, + -996.62253068243604, + -994.89047813392926, + -1000.0866357794496, + -998.35458323094281, + -994.89047813392926, + -1001.8186883279564, + -996.62253068243604 + ], + "z" : [ + 2119.0095568951306, + 2119.0095568951306, + 2122.0095475531025, + 2119.0095568951306, + 2122.0095475531025, + 2119.0095568951306, + 2122.0095475531025, + 2128.0095288690463, + 2122.0095475531025, + 2131.0095195270183 + ] + }, + { + "2dp0_x" : [ 2133.7379999999998, 2133.7379999999998 ], + "2dp0_y" : [ 1934.6019194670521, 1934.6019048054554 ], + "2dp1_x" : [ 2133.7379999999998, 2133.7379999999998 ], + "2dp1_y" : [ 193.39858354553235, 196.39857614077584 ], + "2dp2_x" : [ 2133.7379999999998, 2133.7379999999998 ], + "2dp2_y" : [ 2128.0095288690463, 2131.0095195270183 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2133.7379999999998 ], + "center_y" : [ -1004.4167671507163 ], + "center_z" : [ 2129.5095241980325 ], + "charge" : [ 140.71315002441406 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 1 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 0 ], + "npoints" : [ 2 ], + "slice_index_max" : [ 7080 ], + "slice_index_min" : [ 7076 ], + "u_wire_index_max" : [ 984 ], + "u_wire_index_min" : [ 983 ], + "v_wire_index_max" : [ 399 ], + "v_wire_index_min" : [ 397 ], + "w_wire_index_max" : [ 710 ], + "w_wire_index_min" : [ 708 ], + "wpid" : [ 7 ] + }, + "t" : [ 3538000.0, 3538000.0 ], + "ucharge_unc" : [ 1046.0, 1046.0 ], + "ucharge_val" : [ 291.0, 291.0 ], + "uwire_index" : [ 983, 983 ], + "vcharge_unc" : [ 1552.0, 1433.0 ], + "vcharge_val" : [ 2747.0, 5821.0 ], + "vwire_index" : [ 397, 398 ], + "wcharge_unc" : [ 196.0, 0.0 ], + "wcharge_val" : [ 338.0, 0.0 ], + "wpid" : [ 7, 7 ], + "wwire_index" : [ 709, 710 ], + "x" : [ 2133.7379999999998, 2133.7379999999998 ], + "x_t0cor" : [ 2133.7379999999998, 2133.7379999999998 ], + "y" : [ -1005.2827934249697, -1003.5507408764629 ], + "z" : [ 2128.0095288690463, 2131.0095195270183 ] + }, + { + "2dp0_x" : [ + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001 + ], + "2dp0_y" : [ + 1910.6019347203148, + 1910.6019200587182, + 1916.6019199108018, + 1916.6019052492052, + 1919.6019564908352, + 1919.6019271676419, + 1919.6019125060452, + 1919.6018978444486, + 1925.6019416813219, + 1925.601897696532, + 1925.6018830349353 + ], + "2dp1_x" : [ + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001 + ], + "2dp1_y" : [ + 202.39867862403548, + 205.39867121927887, + 202.39864930084241, + 205.39864189608579, + 193.39865685351526, + 199.39864204400237, + 202.39863463924576, + 205.39862723448914, + 193.39862753032219, + 202.39860531605234, + 205.39859791129584 + ], + "2dp2_x" : [ + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001 + ], + "2dp2_y" : [ + 2113.0095755791867, + 2116.0095662371587, + 2119.0095568951306, + 2122.0095475531025, + 2113.0095755791867, + 2119.0095568951306, + 2122.0095475531025, + 2125.0095382110744, + 2119.0095568951306, + 2128.0095288690463, + 2131.0095195270183 + ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2135.9399999999996 ], + "center_y" : [ -991.58383235950726 ], + "center_z" : [ 2120.6459154358422 ], + "charge" : [ 16627.005859375 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 11 ], + "slice_index_max" : [ 7084 ], + "slice_index_min" : [ 7080 ], + "u_wire_index_max" : [ 981 ], + "u_wire_index_min" : [ 975 ], + "v_wire_index_max" : [ 402 ], + "v_wire_index_min" : [ 397 ], + "w_wire_index_max" : [ 710 ], + "w_wire_index_min" : [ 704 ], + "wpid" : [ 7 ] + }, + "t" : [ + 3540000.0, + 3540000.0, + 3540000.0, + 3540000.0, + 3540000.0, + 3540000.0, + 3540000.0, + 3540000.0, + 3540000.0, + 3540000.0, + 3540000.0 + ], + "ucharge_unc" : [ + 932.0, + 932.0, + 1008.0, + 1008.0, + 971.0, + 971.0, + 971.0, + 971.0, + 1046.0, + 1046.0, + 1046.0 + ], + "ucharge_val" : [ + 1498.0, + 1498.0, + 5513.0, + 5513.0, + 5392.0, + 5392.0, + 5392.0, + 5392.0, + 2780.0, + 2780.0, + 2780.0 + ], + "uwire_index" : [ 975, 975, 977, 977, 978, 978, 978, 978, 980, 980, 980 ], + "vcharge_unc" : [ + 1367.0, + 1433.0, + 1367.0, + 1433.0, + 1552.0, + 1552.0, + 1367.0, + 1433.0, + 1552.0, + 1367.0, + 1433.0 + ], + "vcharge_val" : [ + 8388.0, + 4810.0, + 8388.0, + 4810.0, + 689.0, + 5859.0, + 8388.0, + 4810.0, + 689.0, + 8388.0, + 4810.0 + ], + "vwire_index" : [ 400, 401, 400, 401, 397, 399, 400, 401, 397, 400, 401 ], + "wcharge_unc" : [ + 196.0, + 196.0, + 196.0, + 195.0, + 196.0, + 196.0, + 195.0, + 196.0, + 196.0, + 196.0, + 0.0 + ], + "wcharge_val" : [ + 188.0, + 3102.0, + 10574.0, + 6837.0, + 188.0, + 10574.0, + 6837.0, + 639.0, + 10574.0, + 334.0, + 0.0 + ], + "wpid" : [ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 ], + "wwire_index" : [ 704, 705, 706, 707, 704, 706, 707, 708, 706, 709, 710 ], + "x" : [ + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001 + ], + "x_t0cor" : [ + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001 + ], + "y" : [ + -986.2302153913954, + -984.49816284288863, + -989.69432048840895, + -987.96226793990218, + -996.62253068243604, + -993.15842558542249, + -991.42637303691572, + -989.69432048840895, + -1000.0866357794496, + -994.89047813392926, + -993.15842558542249 + ], + "z" : [ + 2113.0095755791867, + 2116.0095662371587, + 2119.0095568951306, + 2122.0095475531025, + 2113.0095755791867, + 2119.0095568951306, + 2122.0095475531025, + 2125.0095382110744, + 2119.0095568951306, + 2128.0095288690463, + 2131.0095195270183 + ] + }, + { + "2dp0_x" : [ 2135.9400000000001, 2135.9400000000001 ], + "2dp0_y" : [ 1910.6018907355251, 1919.6018685212553 ], + "2dp1_x" : [ 2135.9400000000001, 2135.9400000000001 ], + "2dp1_y" : [ 211.39865640976598, 211.39861242497591 ], + "2dp2_x" : [ 2135.9400000000001, 2135.9400000000001 ], + "2dp2_y" : [ 2122.0095475531025, 2131.0095195270183 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2135.9400000000001 ], + "center_y" : [ -983.63213656863525 ], + "center_z" : [ 2126.5095335400601 ], + "charge" : [ 1643.651611328125 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 2 ], + "slice_index_max" : [ 7084 ], + "slice_index_min" : [ 7080 ], + "u_wire_index_max" : [ 979 ], + "u_wire_index_min" : [ 975 ], + "v_wire_index_max" : [ 404 ], + "v_wire_index_min" : [ 403 ], + "w_wire_index_max" : [ 710 ], + "w_wire_index_min" : [ 706 ], + "wpid" : [ 7 ] + }, + "t" : [ 3540000.0, 3540000.0 ], + "ucharge_unc" : [ 932.0, 971.0 ], + "ucharge_val" : [ 1498.0, 5392.0 ], + "uwire_index" : [ 975, 978 ], + "vcharge_unc" : [ 1433.0, 1433.0 ], + "vcharge_val" : [ 374.0, 374.0 ], + "vwire_index" : [ 403, 403 ], + "wcharge_unc" : [ 195.0, 0.0 ], + "wcharge_val" : [ 6837.0, 0.0 ], + "wpid" : [ 7, 7 ], + "wwire_index" : [ 707, 710 ], + "x" : [ 2135.9400000000001, 2135.9400000000001 ], + "x_t0cor" : [ 2135.9400000000001, 2135.9400000000001 ], + "y" : [ -981.03405774587509, -986.2302153913954 ], + "z" : [ 2122.0095475531025, 2131.0095195270183 ] + }, + { + "2dp0_x" : [ 2138.1419999999998 ], + "2dp0_y" : [ 1919.6019564908352 ], + "2dp1_x" : [ 2138.1419999999998 ], + "2dp1_y" : [ 193.39865685351526 ], + "2dp2_x" : [ 2138.1419999999998 ], + "2dp2_y" : [ 2113.0095755791867 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2138.1419999999998 ], + "center_y" : [ -996.62253068243604 ], + "center_z" : [ 2113.0095755791867 ], + "charge" : [ 1.0 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 1 ], + "slice_index_max" : [ 7088 ], + "slice_index_min" : [ 7084 ], + "u_wire_index_max" : [ 979 ], + "u_wire_index_min" : [ 978 ], + "v_wire_index_max" : [ 398 ], + "v_wire_index_min" : [ 397 ], + "w_wire_index_max" : [ 705 ], + "w_wire_index_min" : [ 704 ], + "wpid" : [ 7 ] + }, + "t" : [ 3542000.0 ], + "ucharge_unc" : [ 971.0 ], + "ucharge_val" : [ 2708.0 ], + "uwire_index" : [ 978 ], + "vcharge_unc" : [ 1552.0 ], + "vcharge_val" : [ 853.0 ], + "vwire_index" : [ 397 ], + "wcharge_unc" : [ 196.0 ], + "wcharge_val" : [ 570.0 ], + "wpid" : [ 7 ], + "wwire_index" : [ 704 ], + "x" : [ 2138.1419999999998 ], + "x_t0cor" : [ 2138.1419999999998 ], + "y" : [ -996.62253068243604 ], + "z" : [ 2113.0095755791867 ] + }, + { + "2dp0_x" : [ + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998 + ], + "2dp0_y" : [ + 1904.6019129226042, + 1910.6019127746422, + 1913.6019346931241, + 1913.6019200314818, + 1913.6019053698396, + 1913.6018907081973, + 1913.6018760465549, + 1916.6019272883223, + 1916.6019126266801, + 1916.6018979650378, + 1919.6019052218774, + 1919.601890560235 + ], + "2dp1_x" : [ + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998 + ], + "2dp1_y" : [ + 209.88911852099761, + 206.88910594459503, + 200.88909675047785, + 203.88909868775534, + 206.88910062503294, + 209.88910256231043, + 212.88910449958803, + 200.88909143091496, + 203.88909336819245, + 206.88909530546994, + 203.88908804863036, + 206.88908998590784 + ], + "2dp2_x" : [ + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998 + ], + "2dp2_y" : [ + 2114.5000000000064, + 2117.5000000000064, + 2114.5000000000064, + 2117.5000000000064, + 2120.5000000000064, + 2123.5000000000064, + 2126.5000000000064, + 2117.5000000000064, + 2120.5000000000064, + 2123.5000000000064, + 2123.5000000000064, + 2126.5000000000064 + ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2138.1419999999998 ], + "center_y" : [ -986.23573039326538 ], + "center_z" : [ 2120.5000000000068 ], + "charge" : [ 23048.44140625 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 2 ], + "npoints" : [ 12 ], + "slice_index_max" : [ 7088 ], + "slice_index_min" : [ 7084 ], + "u_wire_index_max" : [ 979 ], + "u_wire_index_min" : [ 973 ], + "v_wire_index_max" : [ 405 ], + "v_wire_index_min" : [ 399 ], + "w_wire_index_max" : [ 709 ], + "w_wire_index_min" : [ 704 ], + "wpid" : [ 7 ] + }, + "t" : [ + 3542000.0, + 3542000.0, + 3542000.0, + 3542000.0, + 3542000.0, + 3542000.0, + 3542000.0, + 3542000.0, + 3542000.0, + 3542000.0, + 3542000.0, + 3542000.0 + ], + "ucharge_unc" : [ + 1008.0, + 932.0, + 1081.0, + 1081.0, + 1081.0, + 1081.0, + 1081.0, + 1008.0, + 1008.0, + 1008.0, + 971.0, + 971.0 + ], + "ucharge_val" : [ + 1208.0, + 6226.0, + 5347.0, + 5347.0, + 5347.0, + 5347.0, + 5347.0, + 5073.0, + 5073.0, + 5073.0, + 2708.0, + 2708.0 + ], + "uwire_index" : [ 973, 975, 976, 976, 976, 976, 976, 977, 977, 977, 978, 978 ], + "vcharge_unc" : [ + 1433.0, + 1367.0, + 1367.0, + 1433.0, + 1367.0, + 1433.0, + 1433.0, + 1367.0, + 1433.0, + 1367.0, + 1433.0, + 1367.0 + ], + "vcharge_val" : [ + 2201.0, + 6648.0, + 6164.0, + 8241.0, + 6648.0, + 2201.0, + 749.0, + 6164.0, + 8241.0, + 6648.0, + 8241.0, + 6648.0 + ], + "vwire_index" : [ 403, 402, 400, 401, 402, 403, 404, 400, 401, 402, 401, 402 ], + "wcharge_unc" : [ + 196.0, + 196.0, + 196.0, + 196.0, + 196.0, + 195.0, + 196.0, + 196.0, + 196.0, + 195.0, + 195.0, + 196.0 + ], + "wcharge_val" : [ + 570.0, + 6333.0, + 570.0, + 6333.0, + 12307.0, + 4265.0, + 605.0, + 6333.0, + 12307.0, + 4265.0, + 4265.0, + 605.0 + ], + "wpid" : [ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 ], + "wwire_index" : [ 704, 705, 704, 705, 706, 707, 708, 705, 706, 707, 707, 708 ], + "x" : [ + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998 + ], + "x_t0cor" : [ + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998 + ], + "y" : [ + -978.44150471227442, + -983.6376569641501, + -988.8338200033146, + -987.10176206116319, + -985.36970411901177, + -983.63764617686036, + -981.90558823470894, + -990.56586715817718, + -988.83380921602577, + -987.10175127387436, + -990.56585637088745, + -988.83379842873603 + ], + "z" : [ + 2114.5000000000064, + 2117.5000000000064, + 2114.5000000000064, + 2117.5000000000064, + 2120.5000000000064, + 2123.5000000000064, + 2126.5000000000064, + 2117.5000000000064, + 2120.5000000000064, + 2123.5000000000064, + 2123.5000000000064, + 2126.5000000000064 + ] + }, + { + "2dp0_x" : [ 2138.1419999999998, 2138.1419999999998 ], + "2dp0_y" : [ 1924.0923838112155, 1927.0923857484931 ], + "2dp1_x" : [ 2138.1419999999998, 2138.1419999999998 ], + "2dp1_y" : [ 193.39863490801486, 193.39862024637239 ], + "2dp2_x" : [ 2138.1419999999998, 2138.1419999999998 ], + "2dp2_y" : [ 2117.4999999999995, 2120.4999999999995 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2138.1419999999998 ], + "center_y" : [ -1000.0811180805543 ], + "center_z" : [ 2118.9999999999995 ], + "charge" : [ 1.0 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 2 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 2 ], + "slice_index_max" : [ 7088 ], + "slice_index_min" : [ 7084 ], + "u_wire_index_max" : [ 981 ], + "u_wire_index_min" : [ 980 ], + "v_wire_index_max" : [ 398 ], + "v_wire_index_min" : [ 397 ], + "w_wire_index_max" : [ 707 ], + "w_wire_index_min" : [ 705 ], + "wpid" : [ 7 ] + }, + "t" : [ 3542000.0, 3542000.0 ], + "ucharge_unc" : [ 1046.0, 0.0 ], + "ucharge_val" : [ 1416.0, 0.0 ], + "uwire_index" : [ 980, 981 ], + "vcharge_unc" : [ 1552.0, 1552.0 ], + "vcharge_val" : [ 853.0, 853.0 ], + "vwire_index" : [ 397, 397 ], + "wcharge_unc" : [ 196.0, 196.0 ], + "wcharge_val" : [ 6333.0, 12307.0 ], + "wpid" : [ 7, 7 ], + "wwire_index" : [ 705, 706 ], + "x" : [ 2138.1419999999998, 2138.1419999999998 ], + "x_t0cor" : [ 2138.1419999999998, 2138.1419999999998 ], + "y" : [ -999.21508910947853, -1000.9471470516302 ], + "z" : [ 2117.4999999999995, 2120.4999999999995 ] + }, + { + "2dp0_x" : [ 2138.1419999999998, 2138.1419999999998 ], + "2dp0_y" : [ 1925.6019123581286, 1925.601897696532 ], + "2dp1_x" : [ 2138.1419999999998, 2138.1419999999998 ], + "2dp1_y" : [ 199.39861272080918, 202.39860531605257 ], + "2dp2_x" : [ 2138.1419999999998, 2138.1419999999998 ], + "2dp2_y" : [ 2125.0095382110744, 2128.0095288690463 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2138.1419999999998 ], + "center_y" : [ -995.75650440818242 ], + "center_z" : [ 2126.5095335400601 ], + "charge" : [ 1069.1107177734375 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 1 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 0 ], + "npoints" : [ 2 ], + "slice_index_max" : [ 7088 ], + "slice_index_min" : [ 7084 ], + "u_wire_index_max" : [ 981 ], + "u_wire_index_min" : [ 980 ], + "v_wire_index_max" : [ 401 ], + "v_wire_index_min" : [ 399 ], + "w_wire_index_max" : [ 709 ], + "w_wire_index_min" : [ 707 ], + "wpid" : [ 7 ] + }, + "t" : [ 3542000.0, 3542000.0 ], + "ucharge_unc" : [ 1046.0, 1046.0 ], + "ucharge_val" : [ 1416.0, 1416.0 ], + "uwire_index" : [ 980, 980 ], + "vcharge_unc" : [ 1552.0, 1367.0 ], + "vcharge_val" : [ 1732.0, 6164.0 ], + "vwire_index" : [ 399, 400 ], + "wcharge_unc" : [ 196.0, 0.0 ], + "wcharge_val" : [ 605.0, 0.0 ], + "wpid" : [ 7, 7 ], + "wwire_index" : [ 708, 709 ], + "x" : [ 2138.1419999999998, 2138.1419999999998 ], + "x_t0cor" : [ 2138.1419999999998, 2138.1419999999998 ], + "y" : [ -996.62253068243581, -994.89047813392904 ], + "z" : [ 2125.0095382110744, 2128.0095288690463 ] + }, + { + "2dp0_x" : [ + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001 + ], + "2dp0_y" : [ + 1901.6019129497945, + 1901.6018982881978, + 1907.6019128018779, + 1907.6018981402817, + 1910.6019347203148, + 1910.6019200587182, + 1910.6019053971218, + 1910.6018907355251, + 1910.6018760739285, + 1913.6019126539618, + 1916.6019199108018, + 1916.6018759260119 + ], + "2dp1_x" : [ + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001 + ], + "2dp1_y" : [ + 211.3987003945557, + 214.39869298979909, + 208.39867847611902, + 211.39867107136263, + 202.39867862403548, + 205.39867121927887, + 208.39866381452248, + 211.39865640976598, + 214.39864900500936, + 205.39865655768244, + 202.39864930084241, + 211.39862708657256 + ], + "2dp2_x" : [ + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001 + ], + "2dp2_y" : [ + 2113.0095755791867, + 2116.0095662371587, + 2116.0095662371587, + 2119.0095568951306, + 2113.0095755791867, + 2116.0095662371587, + 2119.0095568951306, + 2122.0095475531025, + 2125.0095382110744, + 2119.0095568951306, + 2119.0095568951306, + 2128.0095288690463 + ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2140.3440000000005 ], + "center_y" : [ -982.04442173250402 ], + "center_z" : [ 2118.7595576736326 ], + "charge" : [ 28285.017578125 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 12 ], + "slice_index_max" : [ 7092 ], + "slice_index_min" : [ 7088 ], + "u_wire_index_max" : [ 978 ], + "u_wire_index_min" : [ 972 ], + "v_wire_index_max" : [ 405 ], + "v_wire_index_min" : [ 400 ], + "w_wire_index_max" : [ 709 ], + "w_wire_index_min" : [ 704 ], + "wpid" : [ 7 ] + }, + "t" : [ + 3544000.0, + 3544000.0, + 3544000.0, + 3544000.0, + 3544000.0, + 3544000.0, + 3544000.0, + 3544000.0, + 3544000.0, + 3544000.0, + 3544000.0, + 3544000.0 + ], + "ucharge_unc" : [ + 1046.0, + 1046.0, + 1081.0, + 1081.0, + 932.0, + 932.0, + 932.0, + 932.0, + 932.0, + 1081.0, + 1008.0, + 1008.0 + ], + "ucharge_val" : [ + 1573.0, + 1573.0, + 7736.0, + 7736.0, + 9268.0, + 9268.0, + 9268.0, + 9268.0, + 9268.0, + 4440.0, + 2231.0, + 2231.0 + ], + "uwire_index" : [ 972, 972, 974, 974, 975, 975, 975, 975, 975, 976, 977, 977 ], + "vcharge_unc" : [ + 1433.0, + 1433.0, + 1367.0, + 1433.0, + 1367.0, + 1433.0, + 1367.0, + 1433.0, + 1433.0, + 1433.0, + 1367.0, + 1433.0 + ], + "vcharge_val" : [ + 7629.0, + 2618.0, + 11520.0, + 7629.0, + 1942.0, + 6634.0, + 11520.0, + 7629.0, + 2618.0, + 6634.0, + 1942.0, + 7629.0 + ], + "vwire_index" : [ 403, 404, 402, 403, 400, 401, 402, 403, 404, 401, 400, 403 ], + "wcharge_unc" : [ + 196.0, + 196.0, + 196.0, + 196.0, + 196.0, + 196.0, + 196.0, + 195.0, + 196.0, + 196.0, + 196.0, + 0.0 + ], + "wcharge_val" : [ + 1447.0, + 10994.0, + 10994.0, + 12805.0, + 1447.0, + 10994.0, + 12805.0, + 2616.0, + 506.0, + 12805.0, + 12805.0, + 0.0 + ], + "wpid" : [ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 ], + "wwire_index" : [ 704, 705, 705, 706, 704, 705, 706, 707, 708, 706, 706, 709 ], + "x" : [ + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001 + ], + "x_t0cor" : [ + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001 + ], + "y" : [ + -975.83790010035477, + -974.105847551848, + -981.03405774587509, + -979.30200519736832, + -986.2302153913954, + -984.49816284288863, + -982.76611029438186, + -981.03405774587509, + -979.30200519736832, + -986.2302153913954, + -989.69432048840895, + -984.49816284288863 + ], + "z" : [ + 2113.0095755791867, + 2116.0095662371587, + 2116.0095662371587, + 2119.0095568951306, + 2113.0095755791867, + 2116.0095662371587, + 2119.0095568951306, + 2122.0095475531025, + 2125.0095382110744, + 2119.0095568951306, + 2119.0095568951306, + 2128.0095288690463 + ] + }, + { + "2dp0_x" : [ 2140.3440000000001 ], + "2dp0_y" : [ 1925.601897696532 ], + "2dp1_x" : [ 2140.3440000000001 ], + "2dp1_y" : [ 202.39860531605234 ], + "2dp2_x" : [ 2140.3440000000001 ], + "2dp2_y" : [ 2128.0095288690463 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2140.3440000000001 ], + "center_y" : [ -994.89047813392926 ], + "center_z" : [ 2128.0095288690463 ], + "charge" : [ 133.86514282226562 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 1 ], + "slice_index_max" : [ 7092 ], + "slice_index_min" : [ 7088 ], + "u_wire_index_max" : [ 981 ], + "u_wire_index_min" : [ 980 ], + "v_wire_index_max" : [ 401 ], + "v_wire_index_min" : [ 400 ], + "w_wire_index_max" : [ 709 ], + "w_wire_index_min" : [ 708 ], + "wpid" : [ 7 ] + }, + "t" : [ 3544000.0 ], + "ucharge_unc" : [ 1046.0 ], + "ucharge_val" : [ 472.0 ], + "uwire_index" : [ 980 ], + "vcharge_unc" : [ 1367.0 ], + "vcharge_val" : [ 1942.0 ], + "vwire_index" : [ 400 ], + "wcharge_unc" : [ 0.0 ], + "wcharge_val" : [ 0.0 ], + "wpid" : [ 7 ], + "wwire_index" : [ 709 ], + "x" : [ 2140.3440000000001 ], + "x_t0cor" : [ 2140.3440000000001 ], + "y" : [ -994.89047813392926 ], + "z" : [ 2128.0095288690463 ] + }, + { + "2dp0_x" : [ + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998 + ], + "2dp0_y" : [ + 1898.6019056929545, + 1898.6018910313578, + 1901.6018982881978, + 1904.6019055450379, + 1904.6018908834417, + 1907.6019274634748, + 1907.6019128018779, + 1907.6018981402817, + 1907.6018834786851, + 1907.6018688170884, + 1910.6019053971218, + 1916.6019052492052 + ], + "2dp1_x" : [ + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998 + ], + "2dp1_y" : [ + 214.39870765139574, + 217.39870024663912, + 214.39869298979909, + 211.39868573295905, + 214.39867832820266, + 205.39868588087552, + 208.39867847611902, + 211.39867107136263, + 214.39866366660601, + 217.3986562618494, + 208.39866381452248, + 205.39864189608579 + ], + "2dp2_x" : [ + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998 + ], + "2dp2_y" : [ + 2113.0095755791867, + 2116.0095662371587, + 2116.0095662371587, + 2116.0095662371587, + 2119.0095568951306, + 2113.0095755791867, + 2116.0095662371587, + 2119.0095568951306, + 2122.0095475531025, + 2125.0095382110744, + 2119.0095568951306, + 2122.0095475531025 + ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2142.5459999999994 ], + "center_y" : [ -978.14730349836373 ], + "center_z" : [ 2118.0095600091395 ], + "charge" : [ 30240.5546875 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 12 ], + "slice_index_max" : [ 7096 ], + "slice_index_min" : [ 7092 ], + "u_wire_index_max" : [ 978 ], + "u_wire_index_min" : [ 971 ], + "v_wire_index_max" : [ 406 ], + "v_wire_index_min" : [ 401 ], + "w_wire_index_max" : [ 709 ], + "w_wire_index_min" : [ 704 ], + "wpid" : [ 7 ] + }, + "t" : [ + 3546000.0, + 3546000.0, + 3546000.0, + 3546000.0, + 3546000.0, + 3546000.0, + 3546000.0, + 3546000.0, + 3546000.0, + 3546000.0, + 3546000.0, + 3546000.0 + ], + "ucharge_unc" : [ + 971.0, + 971.0, + 1046.0, + 1008.0, + 1008.0, + 1081.0, + 1081.0, + 1081.0, + 1081.0, + 1081.0, + 932.0, + 1008.0 + ], + "ucharge_val" : [ + 2109.0, + 2109.0, + 4688.0, + 5820.0, + 5820.0, + 7839.0, + 7839.0, + 7839.0, + 7839.0, + 7839.0, + 6087.0, + 898.0 + ], + "uwire_index" : [ 971, 971, 972, 973, 973, 974, 974, 974, 974, 974, 975, 977 ], + "vcharge_unc" : [ + 1433.0, + 1492.0, + 1433.0, + 1433.0, + 1433.0, + 1433.0, + 1367.0, + 1433.0, + 1433.0, + 1492.0, + 1367.0, + 1433.0 + ], + "vcharge_val" : [ + 6232.0, + 2541.0, + 6232.0, + 10552.0, + 6232.0, + 3209.0, + 9070.0, + 10552.0, + 6232.0, + 2541.0, + 9070.0, + 3209.0 + ], + "vwire_index" : [ 404, 405, 404, 403, 404, 401, 402, 403, 404, 405, 402, 401 ], + "wcharge_unc" : [ + 196.0, + 196.0, + 196.0, + 196.0, + 196.0, + 196.0, + 196.0, + 196.0, + 195.0, + 196.0, + 196.0, + 195.0 + ], + "wcharge_val" : [ + 3494.0, + 13814.0, + 13814.0, + 13814.0, + 10397.0, + 3494.0, + 13814.0, + 10397.0, + 2062.0, + 481.0, + 10397.0, + 2062.0 + ], + "wpid" : [ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 ], + "wwire_index" : [ 704, 705, 705, 705, 706, 704, 705, 706, 707, 708, 706, 707 ], + "x" : [ + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998 + ], + "x_t0cor" : [ + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998 + ], + "y" : [ + -972.37379500334123, + -970.64174245483446, + -974.105847551848, + -977.56995264886154, + -975.83790010035477, + -982.76611029438186, + -981.03405774587509, + -979.30200519736832, + -977.56995264886154, + -975.83790010035477, + -982.76611029438186, + -987.96226793990218 + ], + "z" : [ + 2113.0095755791867, + 2116.0095662371587, + 2116.0095662371587, + 2116.0095662371587, + 2119.0095568951306, + 2113.0095755791867, + 2116.0095662371587, + 2119.0095568951306, + 2122.0095475531025, + 2125.0095382110744, + 2119.0095568951306, + 2122.0095475531025 + ] + }, + { + "2dp0_x" : [ + 2144.748, + 2144.748, + 2144.748, + 2144.748, + 2144.748, + 2144.748, + 2144.748, + 2144.748, + 2144.748, + 2144.748, + 2144.748, + 2144.748 + ], + "2dp0_y" : [ + 1892.6018911792744, + 1898.6019056929545, + 1898.6018910313578, + 1901.6019129497945, + 1901.6018982881978, + 1901.6018836266016, + 1901.601868965005, + 1904.6019055450379, + 1904.6018908834417, + 1910.6019200587182, + 1910.6018760739285, + 1916.6019052492052 + ], + "2dp1_x" : [ + 2144.748, + 2144.748, + 2144.748, + 2144.748, + 2144.748, + 2144.748, + 2144.748, + 2144.748, + 2144.748, + 2144.748, + 2144.748, + 2144.748 + ], + "2dp1_y" : [ + 220.39872216507592, + 214.39870765139574, + 217.39870024663912, + 211.3987003945557, + 214.39869298979909, + 217.3986855850427, + 220.39867818028608, + 211.39868573295905, + 214.39867832820266, + 205.39867121927887, + 214.39864900500936, + 205.39864189608579 + ], + "2dp2_x" : [ + 2144.748, + 2144.748, + 2144.748, + 2144.748, + 2144.748, + 2144.748, + 2144.748, + 2144.748, + 2144.748, + 2144.748, + 2144.748, + 2144.748 + ], + "2dp2_y" : [ + 2113.0095755791867, + 2113.0095755791867, + 2116.0095662371587, + 2113.0095755791867, + 2116.0095662371587, + 2119.0095568951306, + 2122.0095475531025, + 2116.0095662371587, + 2119.0095568951306, + 2116.0095662371587, + 2125.0095382110744, + 2122.0095475531025 + ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2144.748 ], + "center_y" : [ -975.54922467560357 ], + "center_z" : [ 2117.5095615661444 ], + "charge" : [ 27960.193359375 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 12 ], + "slice_index_max" : [ 7100 ], + "slice_index_min" : [ 7096 ], + "u_wire_index_max" : [ 978 ], + "u_wire_index_min" : [ 969 ], + "v_wire_index_max" : [ 407 ], + "v_wire_index_min" : [ 401 ], + "w_wire_index_max" : [ 709 ], + "w_wire_index_min" : [ 703 ], + "wpid" : [ 7 ] + }, + "t" : [ + 3548000.0, + 3548000.0, + 3548000.0, + 3548000.0, + 3548000.0, + 3548000.0, + 3548000.0, + 3548000.0, + 3548000.0, + 3548000.0, + 3548000.0, + 3548000.0 + ], + "ucharge_unc" : [ + 932.0, + 971.0, + 971.0, + 1046.0, + 1046.0, + 1046.0, + 1046.0, + 1008.0, + 1008.0, + 932.0, + 932.0, + 1008.0 + ], + "ucharge_val" : [ + 1686.0, + 5362.0, + 5362.0, + 6739.0, + 6739.0, + 6739.0, + 6739.0, + 4932.0, + 4932.0, + 1691.0, + 1691.0, + 502.0 + ], + "uwire_index" : [ 969, 971, 971, 972, 972, 972, 972, 973, 973, 975, 975, 977 ], + "vcharge_unc" : [ + 1299.0, + 1433.0, + 1492.0, + 1433.0, + 1433.0, + 1492.0, + 1299.0, + 1433.0, + 1433.0, + 1433.0, + 1433.0, + 1433.0 + ], + "vcharge_val" : [ + 2158.0, + 7965.0, + 7004.0, + 6108.0, + 7965.0, + 7004.0, + 2158.0, + 6108.0, + 7965.0, + 949.0, + 7965.0, + 949.0 + ], + "vwire_index" : [ 406, 404, 405, 403, 404, 405, 406, 403, 404, 401, 404, 401 ], + "wcharge_unc" : [ + 196.0, + 196.0, + 196.0, + 196.0, + 196.0, + 196.0, + 195.0, + 196.0, + 196.0, + 196.0, + 196.0, + 195.0 + ], + "wcharge_val" : [ + 6592.0, + 6592.0, + 12981.0, + 6592.0, + 12981.0, + 5932.0, + 1154.0, + 12981.0, + 5932.0, + 12981.0, + 688.0, + 1154.0 + ], + "wpid" : [ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 ], + "wwire_index" : [ 704, 704, 705, 704, 705, 706, 707, 705, 706, 705, 708, 707 ], + "x" : [ + 2144.748, + 2144.748, + 2144.748, + 2144.748, + 2144.748, + 2144.748, + 2144.748, + 2144.748, + 2144.748, + 2144.748, + 2144.748, + 2144.748 + ], + "x_t0cor" : [ + 2144.748, + 2144.748, + 2144.748, + 2144.748, + 2144.748, + 2144.748, + 2144.748, + 2144.748, + 2144.748, + 2144.748, + 2144.748, + 2144.748 + ], + "y" : [ + -965.44558480931414, + -972.37379500334123, + -970.64174245483446, + -975.83790010035477, + -974.105847551848, + -972.37379500334123, + -970.64174245483446, + -977.56995264886154, + -975.83790010035477, + -984.49816284288863, + -979.30200519736832, + -987.96226793990218 + ], + "z" : [ + 2113.0095755791867, + 2113.0095755791867, + 2116.0095662371587, + 2113.0095755791867, + 2116.0095662371587, + 2119.0095568951306, + 2122.0095475531025, + 2116.0095662371587, + 2119.0095568951306, + 2116.0095662371587, + 2125.0095382110744, + 2122.0095475531025 + ] + }, + { + "2dp0_x" : [ + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998 + ], + "2dp0_y" : [ + 1889.601898584031, + 1889.6018839224344, + 1892.6018911792744, + 1892.6018765176777, + 1895.6018984361144, + 1895.6018837745178, + 1898.6019203545511, + 1898.6019056929545, + 1898.6018910313578, + 1898.6018763697616, + 1898.6018617081647, + 1907.6018981402817, + 1910.6018907355251 + ], + "2dp1_x" : [ + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998 + ], + "2dp1_y" : [ + 220.39873682667258, + 223.39872942191596, + 220.39872216507592, + 223.39871476031931, + 217.39871490823577, + 220.39870750347927, + 211.39871505615235, + 214.39870765139574, + 217.39870024663912, + 220.39869284188273, + 223.39868543712623, + 211.39867107136263, + 211.39865640976598 + ], + "2dp2_x" : [ + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998 + ], + "2dp2_y" : [ + 2110.0095849212148, + 2113.0095755791867, + 2113.0095755791867, + 2116.0095662371587, + 2113.0095755791867, + 2116.0095662371587, + 2110.0095849212148, + 2113.0095755791867, + 2116.0095662371587, + 2119.0095568951306, + 2122.0095475531025, + 2119.0095568951306, + 2122.0095475531025 + ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2146.9500000000003 ], + "center_y" : [ -969.57586396344584 ], + "center_z" : [ 2115.548029212855 ], + "charge" : [ 26776.064453125 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 13 ], + "slice_index_max" : [ 7104 ], + "slice_index_min" : [ 7100 ], + "u_wire_index_max" : [ 976 ], + "u_wire_index_min" : [ 968 ], + "v_wire_index_max" : [ 408 ], + "v_wire_index_min" : [ 403 ], + "w_wire_index_max" : [ 708 ], + "w_wire_index_min" : [ 703 ], + "wpid" : [ 7 ] + }, + "t" : [ + 3550000.0, + 3550000.0, + 3550000.0, + 3550000.0, + 3550000.0, + 3550000.0, + 3550000.0, + 3550000.0, + 3550000.0, + 3550000.0, + 3550000.0, + 3550000.0, + 3550000.0 + ], + "ucharge_unc" : [ + 1008.0, + 1008.0, + 932.0, + 932.0, + 1008.0, + 1008.0, + 971.0, + 971.0, + 971.0, + 971.0, + 971.0, + 1081.0, + 932.0 + ], + "ucharge_val" : [ + 2148.0, + 2148.0, + 6272.0, + 6272.0, + 6579.0, + 6579.0, + 6274.0, + 6274.0, + 6274.0, + 6274.0, + 6274.0, + 1401.0, + 263.0 + ], + "uwire_index" : [ 968, 968, 969, 969, 970, 970, 971, 971, 971, 971, 971, 974, 975 ], + "vcharge_unc" : [ + 1299.0, + 1433.0, + 1299.0, + 1433.0, + 1492.0, + 1299.0, + 1433.0, + 1433.0, + 1492.0, + 1299.0, + 1433.0, + 1433.0, + 1433.0 + ], + "vcharge_val" : [ + 7281.0, + 4888.0, + 7281.0, + 4888.0, + 8079.0, + 7281.0, + 1899.0, + 4538.0, + 8079.0, + 7281.0, + 4888.0, + 1899.0, + 1899.0 + ], + "vwire_index" : [ 406, 407, 406, 407, 405, 406, 403, 404, 405, 406, 407, 403, 403 ], + "wcharge_unc" : [ + 195.0, + 196.0, + 196.0, + 196.0, + 196.0, + 196.0, + 195.0, + 196.0, + 196.0, + 196.0, + 195.0, + 196.0, + 195.0 + ], + "wcharge_val" : [ + 2427.0, + 10572.0, + 10572.0, + 10942.0, + 10572.0, + 10942.0, + 2427.0, + 10572.0, + 10942.0, + 2513.0, + 365.0, + 2513.0, + 365.0 + ], + "wpid" : [ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 ], + "wwire_index" : [ 703, 704, 704, 705, 704, 705, 703, 704, 705, 706, 707, 706, 707 ], + "x" : [ + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998 + ], + "x_t0cor" : [ + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998 + ], + "y" : [ + -963.71353226080737, + -961.9814797123006, + -965.44558480931414, + -963.71353226080737, + -968.90968990632769, + -967.17763735782091, + -974.105847551848, + -972.37379500334123, + -970.64174245483446, + -968.90968990632769, + -967.17763735782091, + -979.30200519736832, + -981.03405774587509 + ], + "z" : [ + 2110.0095849212148, + 2113.0095755791867, + 2113.0095755791867, + 2116.0095662371587, + 2113.0095755791867, + 2116.0095662371587, + 2110.0095849212148, + 2113.0095755791867, + 2116.0095662371587, + 2119.0095568951306, + 2122.0095475531025, + 2119.0095568951306, + 2122.0095475531025 + ] + }, + { + "2dp0_x" : [ 2146.9499999999998, 2146.9499999999998 ], + "2dp0_y" : [ 1889.6018545992413, 1895.601839789728 ], + "2dp1_x" : [ 2146.9499999999998, 2146.9499999999998 ], + "2dp1_y" : [ 229.39871461240296, 229.39868528920965 ], + "2dp2_x" : [ 2146.9499999999998, 2146.9499999999998 ], + "2dp2_y" : [ 2119.0095568951306, 2125.0095382110744 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2146.9499999999998 ], + "center_y" : [ -960.24942716379383 ], + "center_z" : [ 2122.0095475531025 ], + "charge" : [ 79.566566467285156 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 2 ], + "slice_index_max" : [ 7104 ], + "slice_index_min" : [ 7100 ], + "u_wire_index_max" : [ 971 ], + "u_wire_index_min" : [ 968 ], + "v_wire_index_max" : [ 410 ], + "v_wire_index_min" : [ 409 ], + "w_wire_index_max" : [ 708 ], + "w_wire_index_min" : [ 705 ], + "wpid" : [ 7 ] + }, + "t" : [ 3550000.0, 3550000.0 ], + "ucharge_unc" : [ 1008.0, 1008.0 ], + "ucharge_val" : [ 2148.0, 6579.0 ], + "uwire_index" : [ 968, 970 ], + "vcharge_unc" : [ 1492.0, 1492.0 ], + "vcharge_val" : [ 602.0, 602.0 ], + "vwire_index" : [ 409, 409 ], + "wcharge_unc" : [ 196.0, 0.0 ], + "wcharge_val" : [ 2513.0, 0.0 ], + "wpid" : [ 7, 7 ], + "wwire_index" : [ 706, 708 ], + "x" : [ 2146.9499999999998, 2146.9499999999998 ], + "x_t0cor" : [ 2146.9499999999998, 2146.9499999999998 ], + "y" : [ -958.51737461528705, -961.9814797123006 ], + "z" : [ 2119.0095568951306, 2125.0095382110744 ] + }, + { + "2dp0_x" : [ 2149.152, 2149.152 ], + "2dp0_y" : [ 1898.6019203545511, 1901.6019129497945 ], + "2dp1_x" : [ 2149.152, 2149.152 ], + "2dp1_y" : [ 211.39871505615235, 211.3987003945557 ], + "2dp2_x" : [ 2149.152, 2149.152 ], + "2dp2_y" : [ 2110.0095849212148, 2113.0095755791867 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2149.152 ], + "center_y" : [ -974.97187382610139 ], + "center_z" : [ 2111.509580250201 ], + "charge" : [ 825.11480712890625 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 2 ], + "slice_index_max" : [ 7108 ], + "slice_index_min" : [ 7104 ], + "u_wire_index_max" : [ 973 ], + "u_wire_index_min" : [ 971 ], + "v_wire_index_max" : [ 404 ], + "v_wire_index_min" : [ 403 ], + "w_wire_index_max" : [ 705 ], + "w_wire_index_min" : [ 703 ], + "wpid" : [ 7 ] + }, + "t" : [ 3552000.0, 3552000.0 ], + "ucharge_unc" : [ 971.0, 1046.0 ], + "ucharge_val" : [ 3091.0, 1072.0 ], + "uwire_index" : [ 971, 972 ], + "vcharge_unc" : [ 1433.0, 1433.0 ], + "vcharge_val" : [ 331.0, 331.0 ], + "vwire_index" : [ 403, 403 ], + "wcharge_unc" : [ 195.0, 196.0 ], + "wcharge_val" : [ 4793.0, 13973.0 ], + "wpid" : [ 7, 7 ], + "wwire_index" : [ 703, 704 ], + "x" : [ 2149.152, 2149.152 ], + "x_t0cor" : [ 2149.152, 2149.152 ], + "y" : [ -974.105847551848, -975.83790010035477 ], + "z" : [ 2110.0095849212148, 2113.0095755791867 ] + }, + { + "2dp0_x" : [ + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152 + ], + "2dp0_y" : [ + 1880.6018768135109, + 1889.601898584031, + 1889.6018839224344, + 1889.6018692608377, + 1889.6018545992413, + 1892.601905840871, + 1892.6018911792744, + 1892.6018765176777, + 1895.6018984361144, + 1895.6018837745178, + 1898.6018910313578, + 1898.6018470465681, + 1901.6018836266016 + ], + "2dp1_x" : [ + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152 + ], + "2dp1_y" : [ + 229.3987585971928, + 220.39873682667258, + 223.39872942191596, + 226.39872201715934, + 229.39871461240296, + 217.39872956983254, + 220.39872216507592, + 223.39871476031931, + 217.39871490823577, + 220.39870750347927, + 217.39870024663912, + 226.39867803236962, + 217.3986855850427 + ], + "2dp2_x" : [ + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152 + ], + "2dp2_y" : [ + 2110.0095849212148, + 2110.0095849212148, + 2113.0095755791867, + 2116.0095662371587, + 2119.0095568951306, + 2110.0095849212148, + 2113.0095755791867, + 2116.0095662371587, + 2113.0095755791867, + 2116.0095662371587, + 2116.0095662371587, + 2125.0095382110744, + 2119.0095568951306 + ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2149.152000000001 ], + "center_y" : [ -964.51294112934909 ], + "center_z" : [ 2115.0864921885518 ], + "charge" : [ 29263.263671875 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 13 ], + "slice_index_max" : [ 7108 ], + "slice_index_min" : [ 7104 ], + "u_wire_index_max" : [ 973 ], + "u_wire_index_min" : [ 965 ], + "v_wire_index_max" : [ 410 ], + "v_wire_index_min" : [ 405 ], + "w_wire_index_max" : [ 708 ], + "w_wire_index_min" : [ 703 ], + "wpid" : [ 7 ] + }, + "t" : [ + 3552000.0, + 3552000.0, + 3552000.0, + 3552000.0, + 3552000.0, + 3552000.0, + 3552000.0, + 3552000.0, + 3552000.0, + 3552000.0, + 3552000.0, + 3552000.0, + 3552000.0 + ], + "ucharge_unc" : [ + 1046.0, + 1008.0, + 1008.0, + 1008.0, + 1008.0, + 932.0, + 932.0, + 932.0, + 1008.0, + 1008.0, + 971.0, + 971.0, + 1046.0 + ], + "ucharge_val" : [ + 510.0, + 5245.0, + 5245.0, + 5245.0, + 5245.0, + 10444.0, + 10444.0, + 10444.0, + 6976.0, + 6976.0, + 3091.0, + 3091.0, + 1072.0 + ], + "uwire_index" : [ 965, 968, 968, 968, 968, 969, 969, 969, 970, 970, 971, 971, 972 ], + "vcharge_unc" : [ + 1492.0, + 1299.0, + 1433.0, + 1219.0, + 1492.0, + 1492.0, + 1299.0, + 1433.0, + 1492.0, + 1299.0, + 1492.0, + 1219.0, + 1492.0 + ], + "vcharge_val" : [ + 734.0, + 9607.0, + 11535.0, + 2777.0, + 734.0, + 4626.0, + 9607.0, + 11535.0, + 4626.0, + 9607.0, + 4626.0, + 2777.0, + 4626.0 + ], + "vwire_index" : [ 409, 406, 407, 408, 409, 405, 406, 407, 405, 406, 405, 408, 405 ], + "wcharge_unc" : [ + 195.0, + 195.0, + 196.0, + 196.0, + 196.0, + 195.0, + 196.0, + 196.0, + 196.0, + 196.0, + 196.0, + 0.0, + 196.0 + ], + "wcharge_val" : [ + 4793.0, + 4793.0, + 13973.0, + 9545.0, + 1457.0, + 4793.0, + 13973.0, + 9545.0, + 13973.0, + 9545.0, + 9545.0, + 0.0, + 1457.0 + ], + "wpid" : [ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 ], + "wwire_index" : [ 703, 703, 704, 705, 706, 703, 704, 705, 704, 705, 705, 708, 706 ], + "x" : [ + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152 + ], + "x_t0cor" : [ + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152, + 2149.152 + ], + "y" : [ + -953.32121696976674, + -963.71353226080737, + -961.9814797123006, + -960.24942716379383, + -958.51737461528705, + -967.17763735782091, + -965.44558480931414, + -963.71353226080737, + -968.90968990632769, + -967.17763735782091, + -970.64174245483446, + -965.44558480931414, + -972.37379500334123 + ], + "z" : [ + 2110.0095849212148, + 2110.0095849212148, + 2113.0095755791867, + 2116.0095662371587, + 2119.0095568951306, + 2110.0095849212148, + 2113.0095755791867, + 2116.0095662371587, + 2113.0095755791867, + 2116.0095662371587, + 2116.0095662371587, + 2125.0095382110744, + 2119.0095568951306 + ] + }, + { + "2dp0_x" : [ 2149.152, 2149.152 ], + "2dp0_y" : [ 1906.0923402701728, 1909.0923422074504 ], + "2dp1_x" : [ 2149.152, 2149.152 ], + "2dp1_y" : [ 211.39867844905768, 211.39866378741522 ], + "2dp2_x" : [ 2149.152, 2149.152 ], + "2dp2_y" : [ 2117.4999999999995, 2120.4999999999995 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2149.152 ], + "center_y" : [ -979.29648749847036 ], + "center_z" : [ 2118.9999999999995 ], + "charge" : [ 1.0 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 2 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 2 ], + "slice_index_max" : [ 7108 ], + "slice_index_min" : [ 7104 ], + "u_wire_index_max" : [ 975 ], + "u_wire_index_min" : [ 974 ], + "v_wire_index_max" : [ 404 ], + "v_wire_index_min" : [ 403 ], + "w_wire_index_max" : [ 707 ], + "w_wire_index_min" : [ 705 ], + "wpid" : [ 7 ] + }, + "t" : [ 3552000.0, 3552000.0 ], + "ucharge_unc" : [ 1081.0, 0.0 ], + "ucharge_val" : [ 394.0, 0.0 ], + "uwire_index" : [ 974, 975 ], + "vcharge_unc" : [ 1433.0, 1433.0 ], + "vcharge_val" : [ 331.0, 331.0 ], + "vwire_index" : [ 403, 403 ], + "wcharge_unc" : [ 196.0, 196.0 ], + "wcharge_val" : [ 9545.0, 1457.0 ], + "wpid" : [ 7, 7 ], + "wwire_index" : [ 705, 706 ], + "x" : [ 2149.152, 2149.152 ], + "x_t0cor" : [ 2149.152, 2149.152 ], + "y" : [ -978.43045852739453, -980.16251646954618 ], + "z" : [ 2117.4999999999995, 2120.4999999999995 ] + }, + { + "2dp0_x" : [ 2149.152 ], + "2dp0_y" : [ 1907.6018688170884 ], + "2dp1_x" : [ 2149.152 ], + "2dp1_y" : [ 217.3986562618494 ], + "2dp2_x" : [ 2149.152 ], + "2dp2_y" : [ 2125.0095382110744 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2149.152 ], + "center_y" : [ -975.83790010035477 ], + "center_z" : [ 2125.0095382110744 ], + "charge" : [ 174.7344970703125 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 1 ], + "slice_index_max" : [ 7108 ], + "slice_index_min" : [ 7104 ], + "u_wire_index_max" : [ 975 ], + "u_wire_index_min" : [ 974 ], + "v_wire_index_max" : [ 406 ], + "v_wire_index_min" : [ 405 ], + "w_wire_index_max" : [ 708 ], + "w_wire_index_min" : [ 707 ], + "wpid" : [ 7 ] + }, + "t" : [ 3552000.0 ], + "ucharge_unc" : [ 1081.0 ], + "ucharge_val" : [ 394.0 ], + "uwire_index" : [ 974 ], + "vcharge_unc" : [ 1492.0 ], + "vcharge_val" : [ 4626.0 ], + "vwire_index" : [ 405 ], + "wcharge_unc" : [ 0.0 ], + "wcharge_val" : [ 0.0 ], + "wpid" : [ 7 ], + "wwire_index" : [ 708 ], + "x" : [ 2149.152 ], + "x_t0cor" : [ 2149.152 ], + "y" : [ -975.83790010035477 ], + "z" : [ 2125.0095382110744 ] + }, + { + "2dp0_x" : [ + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998 + ], + "2dp0_y" : [ + 1877.6018842182675, + 1883.6018840703509, + 1886.601891327191, + 1886.6018766655943, + 1886.6018620039977, + 1889.601898584031, + 1889.6018839224344, + 1889.6018692608377, + 1892.6018911792744, + 1892.6018765176777, + 1895.6018984361144, + 1895.6018544513247, + 1895.601839789728, + 1901.6018836266016 + ], + "2dp1_x" : [ + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998 + ], + "2dp1_y" : [ + 229.39877325878945, + 226.39875134035265, + 223.39874408351261, + 226.39873667875599, + 229.39872927399938, + 220.39873682667258, + 223.39872942191596, + 226.39872201715934, + 220.39872216507592, + 223.39871476031931, + 217.39871490823577, + 226.39869269396627, + 229.39868528920965, + 217.3986855850427 + ], + "2dp2_x" : [ + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998 + ], + "2dp2_y" : [ + 2107.0095942632429, + 2110.0095849212148, + 2110.0095849212148, + 2113.0095755791867, + 2116.0095662371587, + 2110.0095849212148, + 2113.0095755791867, + 2116.0095662371587, + 2113.0095755791867, + 2116.0095662371587, + 2113.0095755791867, + 2122.0095475531025, + 2125.0095382110744, + 2119.0095568951306 + ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2151.3539999999998 ], + "center_y" : [ -961.85776167312144 ], + "center_z" : [ 2114.5095709081725 ], + "charge" : [ 32427.65234375 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 14 ], + "slice_index_max" : [ 7112 ], + "slice_index_min" : [ 7108 ], + "u_wire_index_max" : [ 973 ], + "u_wire_index_min" : [ 964 ], + "v_wire_index_max" : [ 410 ], + "v_wire_index_min" : [ 405 ], + "w_wire_index_max" : [ 708 ], + "w_wire_index_min" : [ 702 ], + "wpid" : [ 7 ] + }, + "t" : [ + 3554000.0, + 3554000.0, + 3554000.0, + 3554000.0, + 3554000.0, + 3554000.0, + 3554000.0, + 3554000.0, + 3554000.0, + 3554000.0, + 3554000.0, + 3554000.0, + 3554000.0, + 3554000.0 + ], + "ucharge_unc" : [ + 932.0, + 1046.0, + 971.0, + 971.0, + 971.0, + 1008.0, + 1008.0, + 1008.0, + 932.0, + 932.0, + 1008.0, + 1008.0, + 1008.0, + 1046.0 + ], + "ucharge_val" : [ + 1349.0, + 4505.0, + 6263.0, + 6263.0, + 6263.0, + 5977.0, + 5977.0, + 5977.0, + 8213.0, + 8213.0, + 3518.0, + 3518.0, + 3518.0, + 656.0 + ], + "uwire_index" : [ 964, 966, 967, 967, 967, 968, 968, 968, 969, 969, 970, 970, 970, 972 ], + "vcharge_unc" : [ + 1492.0, + 1219.0, + 1433.0, + 1219.0, + 1492.0, + 1299.0, + 1433.0, + 1219.0, + 1299.0, + 1433.0, + 1492.0, + 1219.0, + 1492.0, + 1492.0 + ], + "vcharge_val" : [ + 3462.0, + 8021.0, + 12405.0, + 8021.0, + 3462.0, + 6078.0, + 12405.0, + 8021.0, + 6078.0, + 12405.0, + 1435.0, + 8021.0, + 3462.0, + 1435.0 + ], + "vwire_index" : [ 409, 408, 407, 408, 409, 406, 407, 408, 406, 407, 405, 408, 409, 405 ], + "wcharge_unc" : [ + 195.0, + 195.0, + 195.0, + 196.0, + 196.0, + 195.0, + 196.0, + 196.0, + 196.0, + 196.0, + 196.0, + 195.0, + 0.0, + 196.0 + ], + "wcharge_val" : [ + 975.0, + 8060.0, + 8060.0, + 14269.0, + 7528.0, + 8060.0, + 14269.0, + 7528.0, + 14269.0, + 7528.0, + 14269.0, + 193.0, + 0.0, + 1399.0 + ], + "wpid" : [ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 ], + "wwire_index" : [ 702, 703, 703, 704, 705, 703, 704, 705, 704, 705, 704, 707, 708, 706 ], + "x" : [ + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998 + ], + "x_t0cor" : [ + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998 + ], + "y" : [ + -951.58916442125997, + -956.78532206678028, + -960.24942716379383, + -958.51737461528705, + -956.78532206678028, + -963.71353226080737, + -961.9814797123006, + -960.24942716379383, + -965.44558480931414, + -963.71353226080737, + -968.90968990632769, + -963.71353226080737, + -961.9814797123006, + -972.37379500334123 + ], + "z" : [ + 2107.0095942632429, + 2110.0095849212148, + 2110.0095849212148, + 2113.0095755791867, + 2116.0095662371587, + 2110.0095849212148, + 2113.0095755791867, + 2116.0095662371587, + 2113.0095755791867, + 2116.0095662371587, + 2113.0095755791867, + 2122.0095475531025, + 2125.0095382110744, + 2119.0095568951306 + ] + }, + { + "2dp0_x" : [ 2153.556, 2153.556 ], + "2dp0_y" : [ 1867.0922618899358, 1870.0922638272132 ], + "2dp1_x" : [ 2153.556, 2153.556 ], + "2dp1_y" : [ 241.39879500238897, 241.39878034074684 ], + "2dp2_x" : [ 2153.556, 2153.556 ], + "2dp2_y" : [ 2108.4999999999995, 2111.4999999999995 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2153.556 ], + "center_y" : [ -939.45926270187613 ], + "center_z" : [ 2109.9999999999995 ], + "charge" : [ 363.03857421875 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 2 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 2 ], + "slice_index_max" : [ 7116 ], + "slice_index_min" : [ 7112 ], + "u_wire_index_max" : [ 962 ], + "u_wire_index_min" : [ 961 ], + "v_wire_index_max" : [ 414 ], + "v_wire_index_min" : [ 413 ], + "w_wire_index_max" : [ 704 ], + "w_wire_index_min" : [ 702 ], + "wpid" : [ 7 ] + }, + "t" : [ 3556000.0, 3556000.0 ], + "ucharge_unc" : [ 971.0, 0.0 ], + "ucharge_val" : [ 431.0, 0.0 ], + "uwire_index" : [ 961, 962 ], + "vcharge_unc" : [ 1492.0, 1492.0 ], + "vcharge_val" : [ 382.0, 382.0 ], + "vwire_index" : [ 413, 413 ], + "wcharge_unc" : [ 195.0, 195.0 ], + "wcharge_val" : [ 3041.0, 11063.0 ], + "wpid" : [ 7, 7 ], + "wwire_index" : [ 702, 703 ], + "x" : [ 2153.556, 2153.556 ], + "x_t0cor" : [ 2153.556, 2153.556 ], + "y" : [ -938.59323373080053, -940.32529167295172 ], + "z" : [ 2108.4999999999995, 2111.4999999999995 ] + }, + { + "2dp0_x" : [ + 2153.556, + 2153.556, + 2153.556, + 2153.556, + 2153.556, + 2153.556, + 2153.556, + 2153.556, + 2153.556, + 2153.556, + 2153.556 + ], + "2dp0_y" : [ + 1892.601905840871, + 1886.6018913271907, + 1883.6018840703507, + 1886.6018766655941, + 1892.6018618560811, + 1880.6018768135107, + 1883.6018694087541, + 1877.6018695566704, + 1880.601862151914, + 1877.6018548950738, + 1886.6018326808044 + ], + "2dp1_x" : [ + 2153.556, + 2153.556, + 2153.556, + 2153.556, + 2153.556, + 2153.556, + 2153.556, + 2153.556, + 2153.556, + 2153.556, + 2153.556 + ], + "2dp1_y" : [ + 217.39872956983265, + 223.39874408351284, + 226.39875134035287, + 226.39873667875622, + 226.39870735556315, + 229.39875859719291, + 229.39874393559626, + 232.39876585403306, + 232.39875119243629, + 235.39875844927644, + 235.3987144644866 + ], + "2dp2_x" : [ + 2153.556, + 2153.556, + 2153.556, + 2153.556, + 2153.556, + 2153.556, + 2153.556, + 2153.556, + 2153.556, + 2153.556, + 2153.556 + ], + "2dp2_y" : [ + 2110.0095849212148, + 2110.0095849212148, + 2110.0095849212148, + 2113.0095755791867, + 2119.0095568951306, + 2110.0095849212148, + 2113.0095755791867, + 2110.0095849212148, + 2113.0095755791867, + 2113.0095755791867, + 2122.0095475531025 + ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2153.556 ], + "center_y" : [ -955.99802545382238 ], + "center_z" : [ 2113.0095755791867 ], + "charge" : [ 29541.4140625 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 1 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 0 ], + "npoints" : [ 11 ], + "slice_index_max" : [ 7116 ], + "slice_index_min" : [ 7112 ], + "u_wire_index_max" : [ 970 ], + "u_wire_index_min" : [ 964 ], + "v_wire_index_max" : [ 412 ], + "v_wire_index_min" : [ 405 ], + "w_wire_index_max" : [ 708 ], + "w_wire_index_min" : [ 702 ], + "wpid" : [ 7 ] + }, + "t" : [ + 3556000.0, + 3556000.0, + 3556000.0, + 3556000.0, + 3556000.0, + 3556000.0, + 3556000.0, + 3556000.0, + 3556000.0, + 3556000.0, + 3556000.0 + ], + "ucharge_unc" : [ + 932.0, + 971.0, + 1046.0, + 971.0, + 932.0, + 1046.0, + 1046.0, + 932.0, + 1046.0, + 932.0, + 971.0 + ], + "ucharge_val" : [ + 2727.0, + 5679.0, + 6260.0, + 5679.0, + 2727.0, + 5919.0, + 6260.0, + 4763.0, + 5919.0, + 4763.0, + 5679.0 + ], + "uwire_index" : [ 969, 967, 966, 967, 969, 965, 966, 964, 965, 964, 967 ], + "vcharge_unc" : [ + 1492.0, + 1433.0, + 1219.0, + 1219.0, + 1219.0, + 1492.0, + 1492.0, + 1299.0, + 1299.0, + 1552.0, + 1552.0 + ], + "vcharge_val" : [ + 541.0, + 5500.0, + 7934.0, + 7934.0, + 7934.0, + 7610.0, + 7610.0, + 5117.0, + 5117.0, + 1867.0, + 1867.0 + ], + "vwire_index" : [ 405, 407, 408, 408, 408, 409, 409, 410, 410, 411, 411 ], + "wcharge_unc" : [ + 195.0, + 195.0, + 195.0, + 196.0, + 196.0, + 195.0, + 196.0, + 195.0, + 196.0, + 196.0, + 195.0 + ], + "wcharge_val" : [ + 11063.0, + 11063.0, + 11063.0, + 11293.0, + 810.0, + 11063.0, + 11293.0, + 11063.0, + 11293.0, + 11293.0, + 420.0 + ], + "wpid" : [ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 ], + "wwire_index" : [ 703, 703, 703, 704, 706, 703, 704, 703, 704, 704, 707 ], + "x" : [ + 2153.556, + 2153.556, + 2153.556, + 2153.556, + 2153.556, + 2153.556, + 2153.556, + 2153.556, + 2153.556, + 2153.556, + 2153.556 + ], + "x_t0cor" : [ + 2153.556, + 2153.556, + 2153.556, + 2153.556, + 2153.556, + 2153.556, + 2153.556, + 2153.556, + 2153.556, + 2153.556, + 2153.556 + ], + "y" : [ + -967.17763735782069, + -960.2494271637936, + -956.78532206678005, + -958.51737461528683, + -961.98147971230037, + -953.32121696976651, + -955.05326951827328, + -949.85711187275297, + -951.58916442125974, + -948.12505932424619, + -953.32121696976651 + ], + "z" : [ + 2110.0095849212148, + 2110.0095849212148, + 2110.0095849212148, + 2113.0095755791867, + 2119.0095568951306, + 2110.0095849212148, + 2113.0095755791867, + 2110.0095849212148, + 2113.0095755791867, + 2113.0095755791867, + 2122.0095475531025 + ] + }, + { + "2dp0_x" : [ 2153.556, 2153.556 ], + "2dp0_y" : [ 1877.6018255718809, 1883.6018107623677 ], + "2dp1_x" : [ 2153.556, 2153.556 ], + "2dp1_y" : [ 241.39874363976321, 241.39871431656991 ], + "2dp2_x" : [ 2153.556, 2153.556 ], + "2dp2_y" : [ 2119.0095568951306, 2125.0095382110744 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2153.556 ], + "center_y" : [ -946.39300677573965 ], + "center_z" : [ 2122.0095475531025 ], + "charge" : [ 1.0 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 2 ], + "slice_index_max" : [ 7116 ], + "slice_index_min" : [ 7112 ], + "u_wire_index_max" : [ 967 ], + "u_wire_index_min" : [ 964 ], + "v_wire_index_max" : [ 414 ], + "v_wire_index_min" : [ 413 ], + "w_wire_index_max" : [ 708 ], + "w_wire_index_min" : [ 705 ], + "wpid" : [ 7 ] + }, + "t" : [ 3556000.0, 3556000.0 ], + "ucharge_unc" : [ 932.0, 1046.0 ], + "ucharge_val" : [ 4763.0, 6260.0 ], + "uwire_index" : [ 964, 966 ], + "vcharge_unc" : [ 1492.0, 1492.0 ], + "vcharge_val" : [ 382.0, 382.0 ], + "vwire_index" : [ 413, 413 ], + "wcharge_unc" : [ 196.0, 0.0 ], + "wcharge_val" : [ 810.0, 0.0 ], + "wpid" : [ 7, 7 ], + "wwire_index" : [ 706, 708 ], + "x" : [ 2153.556, 2153.556 ], + "x_t0cor" : [ 2153.556, 2153.556 ], + "y" : [ -944.66095422723288, -948.12505932424642 ], + "z" : [ 2119.0095568951306, 2125.0095382110744 ] + }, + { + "2dp0_x" : [ 2153.556, 2153.556 ], + "2dp0_y" : [ 1898.6018910313578, 1898.6018470465679 ], + "2dp1_x" : [ 2153.556, 2153.556 ], + "2dp1_y" : [ 217.39870024663935, 226.39867803236973 ], + "2dp2_x" : [ 2153.556, 2153.556 ], + "2dp2_y" : [ 2116.0095662371587, 2125.0095382110744 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2153.556 ], + "center_y" : [ -968.04366363207407 ], + "center_z" : [ 2120.5095522241163 ], + "charge" : [ 493.33328247070312 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 1 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 0 ], + "npoints" : [ 2 ], + "slice_index_max" : [ 7116 ], + "slice_index_min" : [ 7112 ], + "u_wire_index_max" : [ 972 ], + "u_wire_index_min" : [ 971 ], + "v_wire_index_max" : [ 409 ], + "v_wire_index_min" : [ 405 ], + "w_wire_index_max" : [ 708 ], + "w_wire_index_min" : [ 704 ], + "wpid" : [ 7 ] + }, + "t" : [ 3556000.0, 3556000.0 ], + "ucharge_unc" : [ 971.0, 971.0 ], + "ucharge_val" : [ 440.0, 440.0 ], + "uwire_index" : [ 971, 971 ], + "vcharge_unc" : [ 1492.0, 1219.0 ], + "vcharge_val" : [ 541.0, 7934.0 ], + "vwire_index" : [ 405, 408 ], + "wcharge_unc" : [ 196.0, 0.0 ], + "wcharge_val" : [ 3836.0, 0.0 ], + "wpid" : [ 7, 7 ], + "wwire_index" : [ 705, 708 ], + "x" : [ 2153.556, 2153.556 ], + "x_t0cor" : [ 2153.556, 2153.556 ], + "y" : [ -970.64174245483423, -965.44558480931391 ], + "z" : [ 2116.0095662371587, 2125.0095382110744 ] + }, + { + "2dp0_x" : [ + 2155.7579999999998, + 2155.7579999999998, + 2155.7579999999998, + 2155.7579999999998, + 2155.7579999999998, + 2155.7579999999998, + 2155.7579999999998, + 2155.7579999999998, + 2155.7579999999998 + ], + "2dp0_y" : [ + 1871.6018624206476, + 1877.6018769343279, + 1877.6018622726856, + 1880.6018841911684, + 1880.6018695295261, + 1880.601854867884, + 1880.6018402062414, + 1886.6018987048485, + 1886.6018547199217 + ], + "2dp1_x" : [ + 2155.7579999999998, + 2155.7579999999998, + 2155.7579999999998, + 2155.7579999999998, + 2155.7579999999998, + 2155.7579999999998, + 2155.7579999999998, + 2155.7579999999998, + 2155.7579999999998 + ], + "2dp1_y" : [ + 236.88919447168371, + 230.88917995800352, + 233.88918189528101, + 227.88917270116303, + 230.88917463844064, + 233.88917657571812, + 236.88917851299573, + 221.88915818748296, + 230.88916399931554 + ], + "2dp2_x" : [ + 2155.7579999999998, + 2155.7579999999998, + 2155.7579999999998, + 2155.7579999999998, + 2155.7579999999998, + 2155.7579999999998, + 2155.7579999999998, + 2155.7579999999998, + 2155.7579999999998 + ], + "2dp2_y" : [ + 2108.5000000000064, + 2108.5000000000064, + 2111.5000000000064, + 2108.5000000000064, + 2111.5000000000064, + 2114.5000000000064, + 2117.5000000000064, + 2108.5000000000064, + 2117.5000000000064 + ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2155.7579999999998 ], + "center_y" : [ -951.88337042952116 ], + "center_z" : [ 2111.8333333333403 ], + "charge" : [ 28523.369140625 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 2 ], + "npoints" : [ 9 ], + "slice_index_max" : [ 7120 ], + "slice_index_min" : [ 7116 ], + "u_wire_index_max" : [ 968 ], + "u_wire_index_min" : [ 962 ], + "v_wire_index_max" : [ 412 ], + "v_wire_index_min" : [ 407 ], + "w_wire_index_max" : [ 706 ], + "w_wire_index_min" : [ 702 ], + "wpid" : [ 7 ] + }, + "t" : [ + 3558000.0, + 3558000.0, + 3558000.0, + 3558000.0, + 3558000.0, + 3558000.0, + 3558000.0, + 3558000.0, + 3558000.0 + ], + "ucharge_unc" : [ 1008.0, 932.0, 932.0, 1046.0, 1046.0, 1046.0, 1046.0, 971.0, 971.0 ], + "ucharge_val" : [ + 1843.0, + 8254.0, + 8254.0, + 8001.0, + 8001.0, + 8001.0, + 8001.0, + 2367.0, + 2367.0 + ], + "uwire_index" : [ 962, 964, 964, 965, 965, 965, 965, 967, 967 ], + "vcharge_unc" : [ 0.0, 1299.0, 1552.0, 1492.0, 1299.0, 1552.0, 0.0, 1433.0, 1299.0 ], + "vcharge_val" : [ 0.0, 10538.0, 6709.0, 7770.0, 10538.0, 6709.0, 0.0, 965.0, 10538.0 ], + "vwire_index" : [ 412, 410, 411, 409, 410, 411, 412, 407, 410 ], + "wcharge_unc" : [ 195.0, 195.0, 195.0, 195.0, 195.0, 196.0, 196.0, 195.0, 196.0 ], + "wcharge_val" : [ + 6650.0, + 6650.0, + 13297.0, + 6650.0, + 13297.0, + 7896.0, + 1090.0, + 6650.0, + 1090.0 + ], + "wpid" : [ 7, 7, 7, 7, 7, 7, 7, 7, 7 ], + "wwire_index" : [ 702, 702, 703, 702, 703, 704, 705, 702, 705 ], + "x" : [ + 2155.7579999999998, + 2155.7579999999998, + 2155.7579999999998, + 2155.7579999999998, + 2155.7579999999998, + 2155.7579999999998, + 2155.7579999999998, + 2155.7579999999998, + 2155.7579999999998 + ], + "x_t0cor" : [ + 2155.7579999999998, + 2155.7579999999998, + 2155.7579999999998, + 2155.7579999999998, + 2155.7579999999998, + 2155.7579999999998, + 2155.7579999999998, + 2155.7579999999998, + 2155.7579999999998 + ], + "y" : [ + -943.80046452942781, + -950.7286747234549, + -948.99661678130349, + -954.1927798204689, + -952.46072187831749, + -950.72866393616607, + -948.99660599401466, + -961.12099001449599, + -955.92481618804175 + ], + "z" : [ + 2108.5000000000064, + 2108.5000000000064, + 2111.5000000000064, + 2108.5000000000064, + 2111.5000000000064, + 2114.5000000000064, + 2117.5000000000064, + 2108.5000000000064, + 2117.5000000000064 + ] + }, + { + "2dp0_x" : [ 2155.7579999999998 ], + "2dp0_y" : [ 1886.6018326808044 ], + "2dp1_x" : [ 2155.7579999999998 ], + "2dp1_y" : [ 235.39871446448649 ], + "2dp2_x" : [ 2155.7579999999998 ], + "2dp2_y" : [ 2122.0095475531025 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2155.7579999999998 ], + "center_y" : [ -953.32121696976674 ], + "center_z" : [ 2122.0095475531025 ], + "charge" : [ 170.69876098632812 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 1 ], + "slice_index_max" : [ 7120 ], + "slice_index_min" : [ 7116 ], + "u_wire_index_max" : [ 968 ], + "u_wire_index_min" : [ 967 ], + "v_wire_index_max" : [ 412 ], + "v_wire_index_min" : [ 411 ], + "w_wire_index_max" : [ 708 ], + "w_wire_index_min" : [ 707 ], + "wpid" : [ 7 ] + }, + "t" : [ 3558000.0 ], + "ucharge_unc" : [ 971.0 ], + "ucharge_val" : [ 2367.0 ], + "uwire_index" : [ 967 ], + "vcharge_unc" : [ 1552.0 ], + "vcharge_val" : [ 6709.0 ], + "vwire_index" : [ 411 ], + "wcharge_unc" : [ 195.0 ], + "wcharge_val" : [ 281.0 ], + "wpid" : [ 7 ], + "wwire_index" : [ 707 ], + "x" : [ 2155.7579999999998 ], + "x_t0cor" : [ 2155.7579999999998 ], + "y" : [ -953.32121696976674 ], + "z" : [ 2122.0095475531025 ] + }, + { + "2dp0_x" : [ 2155.7579999999998, 2155.7579999999998, 2155.7579999999998 ], + "2dp0_y" : [ 1865.6018551909071, 1874.6018329766375, 1877.6018255718809 ], + "2dp1_x" : [ 2155.7579999999998, 2155.7579999999998, 2155.7579999999998 ], + "2dp1_y" : [ 241.3988022861497, 241.39875830135963, 241.39874363976321 ], + "2dp2_x" : [ 2155.7579999999998, 2155.7579999999998, 2155.7579999999998 ], + "2dp2_y" : [ 2107.0095942632429, 2116.0095662371587, 2119.0095568951306 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2155.7579999999998 ], + "center_y" : [ -941.77419997972163 ], + "center_z" : [ 2114.0095724651778 ], + "charge" : [ 471.78237915039062 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 3 ], + "slice_index_max" : [ 7120 ], + "slice_index_min" : [ 7116 ], + "u_wire_index_max" : [ 965 ], + "u_wire_index_min" : [ 960 ], + "v_wire_index_max" : [ 414 ], + "v_wire_index_min" : [ 413 ], + "w_wire_index_max" : [ 706 ], + "w_wire_index_min" : [ 702 ], + "wpid" : [ 7 ] + }, + "t" : [ 3558000.0, 3558000.0, 3558000.0 ], + "ucharge_unc" : [ 1046.0, 971.0, 932.0 ], + "ucharge_val" : [ 1305.0, 3995.0, 8254.0 ], + "uwire_index" : [ 960, 963, 964 ], + "vcharge_unc" : [ 1492.0, 1492.0, 1492.0 ], + "vcharge_val" : [ 1073.0, 1073.0, 1073.0 ], + "vwire_index" : [ 413, 413, 413 ], + "wcharge_unc" : [ 195.0, 196.0, 0.0 ], + "wcharge_val" : [ 6650.0, 1090.0, 0.0 ], + "wpid" : [ 7, 7, 7 ], + "wwire_index" : [ 702, 705, 706 ], + "x" : [ 2155.7579999999998, 2155.7579999999998, 2155.7579999999998 ], + "x_t0cor" : [ 2155.7579999999998, 2155.7579999999998, 2155.7579999999998 ], + "y" : [ -937.73274403320579, -942.92890167872611, -944.66095422723288 ], + "z" : [ 2107.0095942632429, 2116.0095662371587, 2119.0095568951306 ] + }, + { + "2dp0_x" : [ 2155.7579999999998, 2155.7579999999998 ], + "2dp0_y" : [ 1880.6018181671243, 1883.6018107623677 ], + "2dp1_x" : [ 2155.7579999999998, 2155.7579999999998 ], + "2dp1_y" : [ 241.39872897816656, 241.39871431656991 ], + "2dp2_x" : [ 2155.7579999999998, 2155.7579999999998 ], + "2dp2_y" : [ 2122.0095475531025, 2125.0095382110744 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2155.7579999999998 ], + "center_y" : [ -947.25903304999304 ], + "center_z" : [ 2123.5095428820887 ], + "charge" : [ 116.27317810058594 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 2 ], + "slice_index_max" : [ 7120 ], + "slice_index_min" : [ 7116 ], + "u_wire_index_max" : [ 967 ], + "u_wire_index_min" : [ 965 ], + "v_wire_index_max" : [ 414 ], + "v_wire_index_min" : [ 413 ], + "w_wire_index_max" : [ 708 ], + "w_wire_index_min" : [ 707 ], + "wpid" : [ 7 ] + }, + "t" : [ 3558000.0, 3558000.0 ], + "ucharge_unc" : [ 1046.0, 1046.0 ], + "ucharge_val" : [ 8001.0, 3963.0 ], + "uwire_index" : [ 965, 966 ], + "vcharge_unc" : [ 1492.0, 1492.0 ], + "vcharge_val" : [ 1073.0, 1073.0 ], + "vwire_index" : [ 413, 413 ], + "wcharge_unc" : [ 195.0, 0.0 ], + "wcharge_val" : [ 281.0, 0.0 ], + "wpid" : [ 7, 7 ], + "wwire_index" : [ 707, 708 ], + "x" : [ 2155.7579999999998, 2155.7579999999998 ], + "x_t0cor" : [ 2155.7579999999998, 2155.7579999999998 ], + "y" : [ -946.39300677573965, -948.12505932424642 ], + "z" : [ 2122.0095475531025, 2125.0095382110744 ] + }, + { + "2dp0_x" : [ 2157.96, 2157.96 ], + "2dp0_y" : [ 1880.6019061367042, 1883.6018987319476 ], + "2dp1_x" : [ 2157.96, 2157.96 ], + "2dp1_y" : [ 223.39877340670591, 223.39875874510926 ], + "2dp2_x" : [ 2157.96, 2157.96 ], + "2dp2_y" : [ 2104.009603605271, 2107.0095942632429 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2157.96 ], + "center_y" : [ -957.65134834103367 ], + "center_z" : [ 2105.5095989342572 ], + "charge" : [ 502.10971069335938 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 2 ], + "slice_index_max" : [ 7124 ], + "slice_index_min" : [ 7120 ], + "u_wire_index_max" : [ 967 ], + "u_wire_index_min" : [ 965 ], + "v_wire_index_max" : [ 408 ], + "v_wire_index_min" : [ 407 ], + "w_wire_index_max" : [ 703 ], + "w_wire_index_min" : [ 701 ], + "wpid" : [ 7 ] + }, + "t" : [ 3560000.0, 3560000.0 ], + "ucharge_unc" : [ 1046.0, 1046.0 ], + "ucharge_val" : [ 5137.0, 1530.0 ], + "uwire_index" : [ 965, 966 ], + "vcharge_unc" : [ 1433.0, 1433.0 ], + "vcharge_val" : [ 318.0, 318.0 ], + "vwire_index" : [ 407, 407 ], + "wcharge_unc" : [ 195.0, 195.0 ], + "wcharge_val" : [ 1653.0, 11089.0 ], + "wpid" : [ 7, 7 ], + "wwire_index" : [ 701, 702 ], + "x" : [ 2157.96, 2157.96 ], + "x_t0cor" : [ 2157.96, 2157.96 ], + "y" : [ -956.78532206678028, -958.51737461528705 ], + "z" : [ 2104.009603605271, 2107.0095942632429 ] + }, + { + "2dp0_x" : [ + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96 + ], + "2dp0_y" : [ + 1865.6018698525038, + 1865.6018551909071, + 1868.6018624477472, + 1871.6018697045874, + 1871.6018550429908, + 1874.6018916230241, + 1874.6018769614275, + 1874.6018622998308, + 1874.6018476382342, + 1874.6018329766375, + 1877.6018695566709, + 1877.6018548950742, + 1880.6018621519142, + 1883.6018694087543 + ], + "2dp1_x" : [ + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96 + ], + "2dp1_y" : [ + 238.39880969090632, + 241.3988022861497, + 238.39879502930967, + 235.39878777246952, + 238.3987803677129, + 229.3987879203861, + 232.39878051562948, + 235.39877311087287, + 238.39876570611625, + 241.39875830135963, + 232.39876585403283, + 235.39875844927622, + 232.39875119243618, + 229.39874393559603 + ], + "2dp2_x" : [ + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96 + ], + "2dp2_y" : [ + 2104.009603605271, + 2107.0095942632429, + 2107.0095942632429, + 2107.0095942632429, + 2110.0095849212148, + 2104.009603605271, + 2107.0095942632429, + 2110.0095849212148, + 2113.0095755791867, + 2116.0095662371587, + 2110.0095849212148, + 2113.0095755791867, + 2113.0095755791867, + 2113.0095755791867 + ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2157.9599999999996 ], + "center_y" : [ -945.89813461902338 ], + "center_z" : [ 2109.5810148272194 ], + "charge" : [ 31976.70703125 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 14 ], + "slice_index_max" : [ 7124 ], + "slice_index_min" : [ 7120 ], + "u_wire_index_max" : [ 967 ], + "u_wire_index_min" : [ 960 ], + "v_wire_index_max" : [ 414 ], + "v_wire_index_min" : [ 409 ], + "w_wire_index_max" : [ 706 ], + "w_wire_index_min" : [ 701 ], + "wpid" : [ 7 ] + }, + "t" : [ + 3560000.0, + 3560000.0, + 3560000.0, + 3560000.0, + 3560000.0, + 3560000.0, + 3560000.0, + 3560000.0, + 3560000.0, + 3560000.0, + 3560000.0, + 3560000.0, + 3560000.0, + 3560000.0 + ], + "ucharge_unc" : [ + 1046.0, + 1046.0, + 971.0, + 1008.0, + 1008.0, + 971.0, + 971.0, + 971.0, + 971.0, + 971.0, + 932.0, + 932.0, + 1046.0, + 1046.0 + ], + "ucharge_val" : [ + 4024.0, + 4024.0, + 5161.0, + 5747.0, + 5747.0, + 6875.0, + 6875.0, + 6875.0, + 6875.0, + 6875.0, + 7596.0, + 7596.0, + 5137.0, + 1530.0 + ], + "uwire_index" : [ 960, 960, 961, 962, 962, 963, 963, 963, 963, 963, 964, 964, 965, 966 ], + "vcharge_unc" : [ + 1219.0, + 1492.0, + 1219.0, + 1552.0, + 1219.0, + 1492.0, + 1299.0, + 1552.0, + 1219.0, + 1492.0, + 1299.0, + 1552.0, + 1299.0, + 1492.0 + ], + "vcharge_val" : [ + 5704.0, + 3079.0, + 5704.0, + 10722.0, + 5704.0, + 3743.0, + 10171.0, + 10722.0, + 5704.0, + 3079.0, + 10171.0, + 10722.0, + 10171.0, + 3743.0 + ], + "vwire_index" : [ 412, 413, 412, 411, 412, 409, 410, 411, 412, 413, 410, 411, 410, 409 ], + "wcharge_unc" : [ + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 196.0, + 196.0, + 195.0, + 196.0, + 196.0, + 196.0 + ], + "wcharge_val" : [ + 1653.0, + 11089.0, + 11089.0, + 11089.0, + 13843.0, + 1653.0, + 11089.0, + 13843.0, + 5085.0, + 695.0, + 13843.0, + 5085.0, + 5085.0, + 5085.0 + ], + "wpid" : [ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 ], + "wwire_index" : [ 701, 702, 702, 702, 703, 701, 702, 703, 704, 705, 703, 704, 704, 704 ], + "x" : [ + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96 + ], + "x_t0cor" : [ + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96, + 2157.96 + ], + "y" : [ + -939.46479658171256, + -937.73274403320579, + -941.19684913021933, + -944.66095422723288, + -942.92890167872611, + -949.85711187275319, + -948.12505932424642, + -946.39300677573965, + -944.66095422723288, + -942.92890167872611, + -949.85711187275319, + -948.12505932424642, + -951.58916442125997, + -955.05326951827351 + ], + "z" : [ + 2104.009603605271, + 2107.0095942632429, + 2107.0095942632429, + 2107.0095942632429, + 2110.0095849212148, + 2104.009603605271, + 2107.0095942632429, + 2110.0095849212148, + 2113.0095755791867, + 2116.0095662371587, + 2110.0095849212148, + 2113.0095755791867, + 2113.0095755791867, + 2113.0095755791867 + ] + }, + { + "2dp0_x" : [ + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998 + ], + "2dp0_y" : [ + 1865.6018698525038, + 1865.6018551909071, + 1865.6018405293105, + 1868.6018771093441, + 1868.6018624477472, + 1868.6018477861505, + 1871.6018697045874, + 1871.6018550429908, + 1874.6018769614275, + 1874.6018622998308, + 1874.6018329766375, + 1874.6018183150409, + 1883.6018547471576 + ], + "2dp1_x" : [ + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998 + ], + "2dp1_y" : [ + 238.39880969090632, + 241.3988022861497, + 244.39879488139309, + 235.39880243406617, + 238.39879502930967, + 241.39878762455305, + 235.39878777246952, + 238.3987803677129, + 232.39878051562948, + 235.39877311087287, + 241.39875830135963, + 244.39875089660336, + 232.39873653083953 + ], + "2dp2_x" : [ + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998 + ], + "2dp2_y" : [ + 2104.009603605271, + 2107.0095942632429, + 2110.0095849212148, + 2104.009603605271, + 2107.0095942632429, + 2110.0095849212148, + 2107.0095942632429, + 2110.0095849212148, + 2107.0095942632429, + 2110.0095849212148, + 2116.0095662371587, + 2119.0095568951306, + 2116.0095662371587 + ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2160.1619999999998 ], + "center_y" : [ -942.79566686730254 ], + "center_z" : [ 2109.7788164090639 ], + "charge" : [ 32723.025390625 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 13 ], + "slice_index_max" : [ 7128 ], + "slice_index_min" : [ 7124 ], + "u_wire_index_max" : [ 967 ], + "u_wire_index_min" : [ 960 ], + "v_wire_index_max" : [ 415 ], + "v_wire_index_min" : [ 410 ], + "w_wire_index_max" : [ 706 ], + "w_wire_index_min" : [ 701 ], + "wpid" : [ 7 ] + }, + "t" : [ + 3562000.0, + 3562000.0, + 3562000.0, + 3562000.0, + 3562000.0, + 3562000.0, + 3562000.0, + 3562000.0, + 3562000.0, + 3562000.0, + 3562000.0, + 3562000.0, + 3562000.0 + ], + "ucharge_unc" : [ + 1046.0, + 1046.0, + 1046.0, + 971.0, + 971.0, + 971.0, + 1008.0, + 1008.0, + 971.0, + 971.0, + 971.0, + 971.0, + 1046.0 + ], + "ucharge_val" : [ + 10226.0, + 10226.0, + 10226.0, + 9409.0, + 9409.0, + 9409.0, + 7370.0, + 7370.0, + 4776.0, + 4776.0, + 4776.0, + 4776.0, + 598.0 + ], + "uwire_index" : [ 960, 960, 960, 961, 961, 961, 962, 962, 963, 963, 963, 963, 966 ], + "vcharge_unc" : [ + 1219.0, + 1492.0, + 1433.0, + 1552.0, + 1219.0, + 1492.0, + 1552.0, + 1219.0, + 1299.0, + 1552.0, + 1492.0, + 1433.0, + 1299.0 + ], + "vcharge_val" : [ + 9470.0, + 8223.0, + 3175.0, + 8097.0, + 9470.0, + 8223.0, + 8097.0, + 9470.0, + 3814.0, + 8097.0, + 8223.0, + 3175.0, + 3814.0 + ], + "vwire_index" : [ 412, 413, 414, 411, 412, 413, 411, 412, 410, 411, 413, 414, 410 ], + "wcharge_unc" : [ + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 196.0, + 0.0, + 196.0 + ], + "wcharge_val" : [ + 4253.0, + 14937.0, + 10697.0, + 4253.0, + 14937.0, + 10697.0, + 14937.0, + 10697.0, + 14937.0, + 10697.0, + 672.0, + 0.0, + 672.0 + ], + "wpid" : [ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 ], + "wwire_index" : [ 701, 702, 703, 701, 702, 703, 702, 703, 702, 703, 705, 706, 705 ], + "x" : [ + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998 + ], + "x_t0cor" : [ + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998 + ], + "y" : [ + -939.46479658171256, + -937.73274403320579, + -936.00069148469902, + -942.92890167872611, + -941.19684913021933, + -939.46479658171256, + -944.66095422723288, + -942.92890167872611, + -948.12505932424642, + -946.39300677573965, + -942.92890167872611, + -941.19684913021933, + -953.32121696976674 + ], + "z" : [ + 2104.009603605271, + 2107.0095942632429, + 2110.0095849212148, + 2104.009603605271, + 2107.0095942632429, + 2110.0095849212148, + 2107.0095942632429, + 2110.0095849212148, + 2107.0095942632429, + 2110.0095849212148, + 2116.0095662371587, + 2119.0095568951306, + 2116.0095662371587 + ] + }, + { + "2dp0_x" : [ 2160.1619999999998, 2160.1619999999998 ], + "2dp0_y" : [ 1865.6018112061172, 1868.6018038013608 ], + "2dp1_x" : [ 2160.1619999999998, 2160.1619999999998 ], + "2dp1_y" : [ 250.39878007187986, 250.39876541028343 ], + "2dp2_x" : [ 2160.1619999999998, 2160.1619999999998 ], + "2dp2_y" : [ 2116.0095662371587, 2119.0095568951306 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2160.1619999999998 ], + "center_y" : [ -933.40261266193886 ], + "center_z" : [ 2117.5095615661448 ], + "charge" : [ 181.97541809082031 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 2 ], + "slice_index_max" : [ 7128 ], + "slice_index_min" : [ 7124 ], + "u_wire_index_max" : [ 962 ], + "u_wire_index_min" : [ 960 ], + "v_wire_index_max" : [ 417 ], + "v_wire_index_min" : [ 416 ], + "w_wire_index_max" : [ 706 ], + "w_wire_index_min" : [ 704 ], + "wpid" : [ 7 ] + }, + "t" : [ 3562000.0, 3562000.0 ], + "ucharge_unc" : [ 1046.0, 971.0 ], + "ucharge_val" : [ 10226.0, 9409.0 ], + "uwire_index" : [ 960, 961 ], + "vcharge_unc" : [ 1552.0, 1552.0 ], + "vcharge_val" : [ 899.0, 899.0 ], + "vwire_index" : [ 416, 416 ], + "wcharge_unc" : [ 196.0, 0.0 ], + "wcharge_val" : [ 672.0, 0.0 ], + "wpid" : [ 7, 7 ], + "wwire_index" : [ 705, 706 ], + "x" : [ 2160.1619999999998, 2160.1619999999998 ], + "x_t0cor" : [ 2160.1619999999998, 2160.1619999999998 ], + "y" : [ -932.53658638768547, -934.26863893619225 ], + "z" : [ 2116.0095662371587, 2119.0095568951306 ] + }, + { + "2dp0_x" : [ + 2162.364, + 2162.364, + 2162.364, + 2162.364, + 2162.364, + 2162.364, + 2162.364, + 2162.364 + ], + "2dp0_y" : [ + 1874.6018622998304, + 1865.6018698525036, + 1868.601862447747, + 1865.6018551909069, + 1868.6018477861503, + 1865.6018405293103, + 1874.6018183150409, + 1865.601811206117 + ], + "2dp1_x" : [ + 2162.364, + 2162.364, + 2162.364, + 2162.364, + 2162.364, + 2162.364, + 2162.364, + 2162.364 + ], + "2dp1_y" : [ + 235.39877311087309, + 238.39880969090643, + 238.39879502930978, + 241.39880228614982, + 241.39878762455317, + 244.39879488139331, + 244.39875089660347, + 250.39878007188008 + ], + "2dp2_x" : [ + 2162.364, + 2162.364, + 2162.364, + 2162.364, + 2162.364, + 2162.364, + 2162.364, + 2162.364 + ], + "2dp2_y" : [ + 2110.0095849212148, + 2104.009603605271, + 2107.0095942632429, + 2107.0095942632429, + 2110.0095849212148, + 2110.0095849212148, + 2119.0095568951306, + 2116.0095662371587 + ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2162.364 ], + "center_y" : [ -939.24829001314902 ], + "center_z" : [ 2110.3845837534614 ], + "charge" : [ 29463.634765625 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 1 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 0 ], + "npoints" : [ 8 ], + "slice_index_max" : [ 7132 ], + "slice_index_min" : [ 7128 ], + "u_wire_index_max" : [ 964 ], + "u_wire_index_min" : [ 960 ], + "v_wire_index_max" : [ 417 ], + "v_wire_index_min" : [ 411 ], + "w_wire_index_max" : [ 706 ], + "w_wire_index_min" : [ 701 ], + "wpid" : [ 7 ] + }, + "t" : [ + 3564000.0, + 3564000.0, + 3564000.0, + 3564000.0, + 3564000.0, + 3564000.0, + 3564000.0, + 3564000.0 + ], + "ucharge_unc" : [ 971.0, 1046.0, 971.0, 1046.0, 971.0, 1046.0, 971.0, 1046.0 ], + "ucharge_val" : [ 1620.0, 14574.0, 8362.0, 14574.0, 8362.0, 14574.0, 1620.0, 14574.0 ], + "uwire_index" : [ 963, 960, 961, 960, 961, 960, 963, 960 ], + "vcharge_unc" : [ 1552.0, 1219.0, 1219.0, 1492.0, 1492.0, 1433.0, 1433.0, 1552.0 ], + "vcharge_val" : [ 2655.0, 6582.0, 6582.0, 11572.0, 11572.0, 7784.0, 7784.0, 2062.0 ], + "vwire_index" : [ 411, 412, 412, 413, 413, 414, 414, 416 ], + "wcharge_unc" : [ 195.0, 195.0, 195.0, 195.0, 195.0, 195.0, 0.0, 196.0 ], + "wcharge_val" : [ 6378.0, 8356.0, 16131.0, 16131.0, 6378.0, 6378.0, 0.0, 507.0 ], + "wpid" : [ 7, 7, 7, 7, 7, 7, 7, 7 ], + "wwire_index" : [ 703, 701, 702, 702, 703, 703, 706, 705 ], + "x" : [ + 2162.364, + 2162.364, + 2162.364, + 2162.364, + 2162.364, + 2162.364, + 2162.364, + 2162.364 + ], + "x_t0cor" : [ + 2162.364, + 2162.364, + 2162.364, + 2162.364, + 2162.364, + 2162.364, + 2162.364, + 2162.364 + ], + "y" : [ + -946.39300677573942, + -939.46479658171233, + -941.19684913021911, + -937.73274403320556, + -939.46479658171233, + -936.00069148469879, + -941.19684913021911, + -932.53658638768525 + ], + "z" : [ + 2110.0095849212148, + 2104.009603605271, + 2107.0095942632429, + 2107.0095942632429, + 2110.0095849212148, + 2110.0095849212148, + 2119.0095568951306, + 2116.0095662371587 + ] + }, + { + "2dp0_x" : [ 2162.364, 2162.364 ], + "2dp0_y" : [ 1880.6018474903171, 1880.6018328287209 ], + "2dp1_x" : [ 2162.364, 2162.364 ], + "2dp1_y" : [ 235.39874378767979, 238.3987363829234 ], + "2dp2_x" : [ 2162.364, 2162.364 ], + "2dp2_y" : [ 2116.0095662371587, 2119.0095568951306 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2162.364 ], + "center_y" : [ -948.99108559849958 ], + "center_z" : [ 2117.5095615661448 ], + "charge" : [ 758.25860595703125 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 1 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 0 ], + "npoints" : [ 2 ], + "slice_index_max" : [ 7132 ], + "slice_index_min" : [ 7128 ], + "u_wire_index_max" : [ 966 ], + "u_wire_index_min" : [ 965 ], + "v_wire_index_max" : [ 413 ], + "v_wire_index_min" : [ 411 ], + "w_wire_index_max" : [ 706 ], + "w_wire_index_min" : [ 704 ], + "wpid" : [ 7 ] + }, + "t" : [ 3564000.0, 3564000.0 ], + "ucharge_unc" : [ 1046.0, 1046.0 ], + "ucharge_val" : [ 533.0, 533.0 ], + "uwire_index" : [ 965, 965 ], + "vcharge_unc" : [ 1552.0, 1219.0 ], + "vcharge_val" : [ 2655.0, 6582.0 ], + "vwire_index" : [ 411, 412 ], + "wcharge_unc" : [ 196.0, 0.0 ], + "wcharge_val" : [ 507.0, 0.0 ], + "wpid" : [ 7, 7 ], + "wwire_index" : [ 705, 706 ], + "x" : [ 2162.364, 2162.364 ], + "x_t0cor" : [ 2162.364, 2162.364 ], + "y" : [ -949.85711187275297, -948.12505932424619 ], + "z" : [ 2116.0095662371587, 2119.0095568951306 ] + }, + { + "2dp0_x" : [ 2164.5659999999998, 2164.5659999999998 ], + "2dp0_y" : [ 1865.6018845141002, 1868.6018771093441 ], + "2dp1_x" : [ 2164.5659999999998, 2164.5659999999998 ], + "2dp1_y" : [ 235.39881709566271, 235.39880243406617 ], + "2dp2_x" : [ 2164.5659999999998, 2164.5659999999998 ], + "2dp2_y" : [ 2101.009612947299, 2104.009603605271 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2164.5659999999998 ], + "center_y" : [ -942.06287540447272 ], + "center_z" : [ 2102.5096082762848 ], + "charge" : [ 68.581703186035156 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 2 ], + "slice_index_max" : [ 7136 ], + "slice_index_min" : [ 7132 ], + "u_wire_index_max" : [ 962 ], + "u_wire_index_min" : [ 960 ], + "v_wire_index_max" : [ 412 ], + "v_wire_index_min" : [ 411 ], + "w_wire_index_max" : [ 702 ], + "w_wire_index_min" : [ 700 ], + "wpid" : [ 7 ] + }, + "t" : [ 3566000.0, 3566000.0 ], + "ucharge_unc" : [ 1046.0, 971.0 ], + "ucharge_val" : [ 11717.0, 3842.0 ], + "uwire_index" : [ 960, 961 ], + "vcharge_unc" : [ 1552.0, 1552.0 ], + "vcharge_val" : [ 893.0, 893.0 ], + "vwire_index" : [ 411, 411 ], + "wcharge_unc" : [ 195.0, 195.0 ], + "wcharge_val" : [ 2260.0, 13269.0 ], + "wpid" : [ 7, 7 ], + "wwire_index" : [ 700, 701 ], + "x" : [ 2164.5659999999998, 2164.5659999999998 ], + "x_t0cor" : [ 2164.5659999999998, 2164.5659999999998 ], + "y" : [ -941.19684913021933, -942.92890167872611 ], + "z" : [ 2101.009612947299, 2104.009603605271 ] + }, + { + "2dp0_x" : [ 2164.5659999999998, 2164.5659999999998, 2164.5659999999998 ], + "2dp0_y" : [ 1865.6018551909069, 1868.6018477861503, 1865.601811206117 ], + "2dp1_x" : [ 2164.5659999999998, 2164.5659999999998, 2164.5659999999998 ], + "2dp1_y" : [ 241.39880228614982, 241.39878762455317, 250.39878007188008 ], + "2dp2_x" : [ 2164.5659999999998, 2164.5659999999998, 2164.5659999999998 ], + "2dp2_y" : [ 2107.0095942632429, 2110.0095849212148, 2116.0095662371587 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2164.5659999999998 ], + "center_y" : [ -936.57804233420109 ], + "center_z" : [ 2111.0095818072054 ], + "charge" : [ 15686.3564453125 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 1 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 0 ], + "npoints" : [ 3 ], + "slice_index_max" : [ 7136 ], + "slice_index_min" : [ 7132 ], + "u_wire_index_max" : [ 962 ], + "u_wire_index_min" : [ 960 ], + "v_wire_index_max" : [ 417 ], + "v_wire_index_min" : [ 413 ], + "w_wire_index_max" : [ 705 ], + "w_wire_index_min" : [ 701 ], + "wpid" : [ 7 ] + }, + "t" : [ 3566000.0, 3566000.0, 3566000.0 ], + "ucharge_unc" : [ 1046.0, 971.0, 1046.0 ], + "ucharge_val" : [ 11717.0, 3842.0, 11717.0 ], + "uwire_index" : [ 960, 961, 960 ], + "vcharge_unc" : [ 1492.0, 1492.0, 1552.0 ], + "vcharge_val" : [ 8529.0, 8529.0, 6297.0 ], + "vwire_index" : [ 413, 413, 416 ], + "wcharge_unc" : [ 195.0, 195.0, 0.0 ], + "wcharge_val" : [ 15177.0, 3460.0, 0.0 ], + "wpid" : [ 7, 7, 7 ], + "wwire_index" : [ 702, 703, 705 ], + "x" : [ 2164.5659999999998, 2164.5659999999998, 2164.5659999999998 ], + "x_t0cor" : [ 2164.5659999999998, 2164.5659999999998, 2164.5659999999998 ], + "y" : [ -937.73274403320556, -939.46479658171233, -932.53658638768525 ], + "z" : [ 2107.0095942632429, 2110.0095849212148, 2116.0095662371587 ] + }, + { + "2dp0_x" : [ 2164.5659999999998, 2164.5659999999998 ], + "2dp0_y" : [ 1873.0922764036168, 1876.0922783408942 ], + "2dp1_x" : [ 2164.5659999999998, 2164.5659999999998 ], + "2dp1_y" : [ 235.39878048870798, 235.39876582706586 ], + "2dp2_x" : [ 2164.5659999999998, 2164.5659999999998 ], + "2dp2_y" : [ 2108.4999999999995, 2111.4999999999995 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2164.5659999999998 ], + "center_y" : [ -946.38747289590412 ], + "center_z" : [ 2109.9999999999995 ], + "charge" : [ 111.03215026855469 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 2 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 2 ], + "slice_index_max" : [ 7136 ], + "slice_index_min" : [ 7132 ], + "u_wire_index_max" : [ 964 ], + "u_wire_index_min" : [ 963 ], + "v_wire_index_max" : [ 412 ], + "v_wire_index_min" : [ 411 ], + "w_wire_index_max" : [ 704 ], + "w_wire_index_min" : [ 702 ], + "wpid" : [ 7 ] + }, + "t" : [ 3566000.0, 3566000.0 ], + "ucharge_unc" : [ 971.0, 0.0 ], + "ucharge_val" : [ 393.0, 0.0 ], + "uwire_index" : [ 963, 964 ], + "vcharge_unc" : [ 1552.0, 1552.0 ], + "vcharge_val" : [ 893.0, 893.0 ], + "vwire_index" : [ 411, 411 ], + "wcharge_unc" : [ 195.0, 195.0 ], + "wcharge_val" : [ 15177.0, 3460.0 ], + "wpid" : [ 7, 7 ], + "wwire_index" : [ 702, 703 ], + "x" : [ 2164.5659999999998, 2164.5659999999998 ], + "x_t0cor" : [ 2164.5659999999998, 2164.5659999999998 ], + "y" : [ -945.52144392482853, -947.25350186697972 ], + "z" : [ 2108.4999999999995, 2111.4999999999995 ] + }, + { + "2dp0_x" : [ 2164.5659999999998 ], + "2dp0_y" : [ 1874.6018329766375 ], + "2dp1_x" : [ 2164.5659999999998 ], + "2dp1_y" : [ 241.39875830135963 ], + "2dp2_x" : [ 2164.5659999999998 ], + "2dp2_y" : [ 2116.0095662371587 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2164.5659999999998 ], + "center_y" : [ -942.92890167872611 ], + "center_z" : [ 2116.0095662371587 ], + "charge" : [ 194.87944030761719 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 1 ], + "slice_index_max" : [ 7136 ], + "slice_index_min" : [ 7132 ], + "u_wire_index_max" : [ 964 ], + "u_wire_index_min" : [ 963 ], + "v_wire_index_max" : [ 414 ], + "v_wire_index_min" : [ 413 ], + "w_wire_index_max" : [ 705 ], + "w_wire_index_min" : [ 704 ], + "wpid" : [ 7 ] + }, + "t" : [ 3566000.0 ], + "ucharge_unc" : [ 971.0 ], + "ucharge_val" : [ 393.0 ], + "uwire_index" : [ 963 ], + "vcharge_unc" : [ 1492.0 ], + "vcharge_val" : [ 8529.0 ], + "vwire_index" : [ 413 ], + "wcharge_unc" : [ 0.0 ], + "wcharge_val" : [ 0.0 ], + "wpid" : [ 7 ], + "wwire_index" : [ 705 ], + "x" : [ 2164.5659999999998 ], + "x_t0cor" : [ 2164.5659999999998 ], + "y" : [ -942.92890167872611 ], + "z" : [ 2116.0095662371587 ] + }, + { + "2dp0_x" : [ 2164.5659999999998 ], + "2dp0_y" : [ 1880.6018474903176 ], + "2dp1_x" : [ 2164.5659999999998 ], + "2dp1_y" : [ 235.39874378767956 ], + "2dp2_x" : [ 2164.5659999999998 ], + "2dp2_y" : [ 2116.0095662371587 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2164.5659999999998 ], + "center_y" : [ -949.85711187275319 ], + "center_z" : [ 2116.0095662371587 ], + "charge" : [ 188.10984802246094 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 1 ], + "slice_index_max" : [ 7136 ], + "slice_index_min" : [ 7132 ], + "u_wire_index_max" : [ 966 ], + "u_wire_index_min" : [ 965 ], + "v_wire_index_max" : [ 412 ], + "v_wire_index_min" : [ 411 ], + "w_wire_index_max" : [ 705 ], + "w_wire_index_min" : [ 704 ], + "wpid" : [ 7 ] + }, + "t" : [ 3566000.0 ], + "ucharge_unc" : [ 1046.0 ], + "ucharge_val" : [ 345.0 ], + "uwire_index" : [ 965 ], + "vcharge_unc" : [ 1552.0 ], + "vcharge_val" : [ 893.0 ], + "vwire_index" : [ 411 ], + "wcharge_unc" : [ 0.0 ], + "wcharge_val" : [ 0.0 ], + "wpid" : [ 7 ], + "wwire_index" : [ 705 ], + "x" : [ 2164.5659999999998 ], + "x_t0cor" : [ 2164.5659999999998 ], + "y" : [ -949.85711187275319 ], + "z" : [ 2116.0095662371587 ] + }, + { + "2dp0_x" : [ 2166.768 ], + "2dp0_y" : [ 1865.6018845141002 ], + "2dp1_x" : [ 2166.768 ], + "2dp1_y" : [ 235.39881709566271 ], + "2dp2_x" : [ 2166.768 ], + "2dp2_y" : [ 2101.009612947299 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2166.768 ], + "center_y" : [ -941.19684913021933 ], + "center_z" : [ 2101.009612947299 ], + "charge" : [ 1.0 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 1 ], + "slice_index_max" : [ 7140 ], + "slice_index_min" : [ 7136 ], + "u_wire_index_max" : [ 961 ], + "u_wire_index_min" : [ 960 ], + "v_wire_index_max" : [ 412 ], + "v_wire_index_min" : [ 411 ], + "w_wire_index_max" : [ 701 ], + "w_wire_index_min" : [ 700 ], + "wpid" : [ 7 ] + }, + "t" : [ 3568000.0 ], + "ucharge_unc" : [ 1046.0 ], + "ucharge_val" : [ 5441.0 ], + "uwire_index" : [ 960 ], + "vcharge_unc" : [ 1552.0 ], + "vcharge_val" : [ 427.0 ], + "vwire_index" : [ 411 ], + "wcharge_unc" : [ 195.0 ], + "wcharge_val" : [ 5224.0 ], + "wpid" : [ 7 ], + "wwire_index" : [ 700 ], + "x" : [ 2166.768 ], + "x_t0cor" : [ 2166.768 ], + "y" : [ -941.19684913021933 ], + "z" : [ 2101.009612947299 ] + }, + { + "2dp0_x" : [ 2166.768, 2166.768 ], + "2dp0_y" : [ 1865.6018551909069, 1865.601811206117 ], + "2dp1_x" : [ 2166.768, 2166.768 ], + "2dp1_y" : [ 241.39880228614982, 250.39878007188008 ], + "2dp2_x" : [ 2166.768, 2166.768 ], + "2dp2_y" : [ 2107.0095942632429, 2116.0095662371587 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2166.768 ], + "center_y" : [ -935.13466521044541 ], + "center_z" : [ 2111.509580250201 ], + "charge" : [ 5467.5732421875 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 1 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 0 ], + "npoints" : [ 2 ], + "slice_index_max" : [ 7140 ], + "slice_index_min" : [ 7136 ], + "u_wire_index_max" : [ 961 ], + "u_wire_index_min" : [ 960 ], + "v_wire_index_max" : [ 417 ], + "v_wire_index_min" : [ 413 ], + "w_wire_index_max" : [ 705 ], + "w_wire_index_min" : [ 701 ], + "wpid" : [ 7 ] + }, + "t" : [ 3568000.0, 3568000.0 ], + "ucharge_unc" : [ 1046.0, 1046.0 ], + "ucharge_val" : [ 5441.0, 5441.0 ], + "uwire_index" : [ 960, 960 ], + "vcharge_unc" : [ 1492.0, 1552.0 ], + "vcharge_val" : [ 3302.0, 12232.0 ], + "vwire_index" : [ 413, 416 ], + "wcharge_unc" : [ 195.0, 0.0 ], + "wcharge_val" : [ 11867.0, 0.0 ], + "wpid" : [ 7, 7 ], + "wwire_index" : [ 702, 705 ], + "x" : [ 2166.768, 2166.768 ], + "x_t0cor" : [ 2166.768, 2166.768 ], + "y" : [ -937.73274403320556, -932.53658638768525 ], + "z" : [ 2107.0095942632429, 2116.0095662371587 ] + }, + { + "2dp0_x" : [ + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768 + ], + "2dp0_y" : [ + 1847.60184097306, + 1850.6018482299, + 1850.6018335683034, + 1853.6018554867401, + 1853.6018408251434, + 1853.6018261635468, + 1856.6018627435801, + 1856.6018480819837, + 1856.6018334203868, + 1856.6018187587902, + 1859.6018553388237, + 1859.6018406772271, + 1862.6018625956638, + 1862.6018479340671, + 1862.6018186108738, + 1862.6018039492772 + ], + "2dp1_x" : [ + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768 + ], + "2dp1_y" : [ + 253.39886063670303, + 250.398853379863, + 253.39884597510661, + 247.39884612302296, + 250.39883871826657, + 253.39883131350996, + 244.39883886618281, + 247.39883146142643, + 250.39882405666992, + 253.39881665191331, + 244.39882420458639, + 247.39881679982977, + 241.39881694774635, + 244.39880954298974, + 250.39879473347651, + 253.39878732871989 + ], + "2dp2_x" : [ + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768 + ], + "2dp2_y" : [ + 2101.009612947299, + 2101.009612947299, + 2104.009603605271, + 2101.009612947299, + 2104.009603605271, + 2107.0095942632429, + 2101.009612947299, + 2104.009603605271, + 2107.0095942632429, + 2110.0095849212148, + 2104.009603605271, + 2107.0095942632429, + 2104.009603605271, + 2107.0095942632429, + 2113.0095755791867, + 2116.0095662371587 + ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2166.768 ], + "center_y" : [ -928.09820173213677 ], + "center_z" : [ 2105.6970983503807 ], + "charge" : [ 30907.99609375 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 16 ], + "slice_index_max" : [ 7140 ], + "slice_index_min" : [ 7136 ], + "u_wire_index_max" : [ 960 ], + "u_wire_index_min" : [ 954 ], + "v_wire_index_max" : [ 418 ], + "v_wire_index_min" : [ 413 ], + "w_wire_index_max" : [ 705 ], + "w_wire_index_min" : [ 700 ], + "wpid" : [ 7 ] + }, + "t" : [ + 3568000.0, + 3568000.0, + 3568000.0, + 3568000.0, + 3568000.0, + 3568000.0, + 3568000.0, + 3568000.0, + 3568000.0, + 3568000.0, + 3568000.0, + 3568000.0, + 3568000.0, + 3568000.0, + 3568000.0, + 3568000.0 + ], + "ucharge_unc" : [ + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0 + ], + "ucharge_val" : [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + "uwire_index" : [ + 954, + 955, + 955, + 956, + 956, + 956, + 957, + 957, + 957, + 957, + 958, + 958, + 959, + 959, + 959, + 959 + ], + "vcharge_unc" : [ + 1367.0, + 1552.0, + 1367.0, + 1219.0, + 1552.0, + 1367.0, + 1433.0, + 1219.0, + 1552.0, + 1367.0, + 1433.0, + 1219.0, + 1492.0, + 1433.0, + 1552.0, + 1367.0 + ], + "vcharge_val" : [ + 4229.0, + 12232.0, + 4229.0, + 13132.0, + 12232.0, + 4229.0, + 4828.0, + 13132.0, + 12232.0, + 4229.0, + 4828.0, + 13132.0, + 3302.0, + 4828.0, + 12232.0, + 4229.0 + ], + "vwire_index" : [ + 417, + 416, + 417, + 415, + 416, + 417, + 414, + 415, + 416, + 417, + 414, + 415, + 413, + 414, + 416, + 417 + ], + "wcharge_unc" : [ + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 196.0, + 0.0 + ], + "wcharge_val" : [ + 5224.0, + 5224.0, + 16576.0, + 5224.0, + 16576.0, + 11867.0, + 5224.0, + 16576.0, + 11867.0, + 2511.0, + 16576.0, + 11867.0, + 16576.0, + 11867.0, + 186.0, + 0.0 + ], + "wpid" : [ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 ], + "wwire_index" : [ + 700, + 700, + 701, + 700, + 701, + 702, + 700, + 701, + 702, + 703, + 701, + 702, + 701, + 702, + 704, + 705 + ], + "x" : [ + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768 + ], + "x_t0cor" : [ + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2166.768 + ], + "y" : [ + -920.41221854813807, + -923.87632364515161, + -922.14427109664484, + -927.34042874216516, + -925.60837619365839, + -923.87632364515161, + -930.8045338391787, + -929.07248129067193, + -927.34042874216516, + -925.60837619365839, + -932.53658638768547, + -930.8045338391787, + -936.00069148469902, + -934.26863893619225, + -930.8045338391787, + -929.07248129067193 + ], + "z" : [ + 2101.009612947299, + 2101.009612947299, + 2104.009603605271, + 2101.009612947299, + 2104.009603605271, + 2107.0095942632429, + 2101.009612947299, + 2104.009603605271, + 2107.0095942632429, + 2110.0095849212148, + 2104.009603605271, + 2107.0095942632429, + 2104.009603605271, + 2107.0095942632429, + 2113.0095755791867, + 2116.0095662371587 + ] + }, + { + "2dp0_x" : [ 2168.9699999999998, 2168.9699999999998 ], + "2dp0_y" : [ 1865.6018258677136, 1865.601811206117 ], + "2dp1_x" : [ 2168.9699999999998, 2168.9699999999998 ], + "2dp1_y" : [ 247.3987874766367, 250.39878007188008 ], + "2dp2_x" : [ 2168.9699999999998, 2168.9699999999998 ], + "2dp2_y" : [ 2113.0095755791867, 2116.0095662371587 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2168.9699999999998 ], + "center_y" : [ -933.40261266193863 ], + "center_z" : [ 2114.5095709081725 ], + "charge" : [ 2100.4814453125 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 1 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 0 ], + "npoints" : [ 2 ], + "slice_index_max" : [ 7144 ], + "slice_index_min" : [ 7140 ], + "u_wire_index_max" : [ 961 ], + "u_wire_index_min" : [ 960 ], + "v_wire_index_max" : [ 417 ], + "v_wire_index_min" : [ 415 ], + "w_wire_index_max" : [ 705 ], + "w_wire_index_min" : [ 703 ], + "wpid" : [ 7 ] + }, + "t" : [ 3570000.0, 3570000.0 ], + "ucharge_unc" : [ 1046.0, 1046.0 ], + "ucharge_val" : [ 1935.0, 1935.0 ], + "uwire_index" : [ 960, 960 ], + "vcharge_unc" : [ 1219.0, 1552.0 ], + "vcharge_val" : [ 7845.0, 10919.0 ], + "vwire_index" : [ 415, 416 ], + "wcharge_unc" : [ 196.0, 0.0 ], + "wcharge_val" : [ 369.0, 0.0 ], + "wpid" : [ 7, 7 ], + "wwire_index" : [ 704, 705 ], + "x" : [ 2168.9699999999998, 2168.9699999999998 ], + "x_t0cor" : [ 2168.9699999999998, 2168.9699999999998 ], + "y" : [ -934.26863893619202, -932.53658638768525 ], + "z" : [ 2113.0095755791867, 2116.0095662371587 ] + }, + { + "2dp0_x" : [ + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998 + ], + "2dp0_y" : [ + 1841.6018411209766, + 1844.60183371622, + 1847.60184097306, + 1847.6018263114634, + 1850.6018628914967, + 1850.6018482299, + 1850.6018335683034, + 1850.6018189067067, + 1853.6018554867401, + 1853.6018408251434, + 1853.6018261635468, + 1856.6018480819837, + 1856.6018334203868, + 1859.6018406772271, + 1859.6017966924369, + 1862.6018332724705 + ], + "2dp1_x" : [ + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998 + ], + "2dp1_y" : [ + 256.39888255513972, + 256.39886789354307, + 253.39886063670303, + 256.39885323194665, + 247.39886078461961, + 250.398853379863, + 253.39884597510661, + 256.39883857034999, + 247.39884612302296, + 250.39883871826657, + 253.39883131350996, + 247.39883146142643, + 250.39882405666992, + 247.39881679982977, + 256.39879458556004, + 247.39880213823312 + ], + "2dp2_x" : [ + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998 + ], + "2dp2_y" : [ + 2098.0096222893271, + 2101.009612947299, + 2101.009612947299, + 2104.009603605271, + 2098.0096222893271, + 2101.009612947299, + 2104.009603605271, + 2107.0095942632429, + 2101.009612947299, + 2104.009603605271, + 2107.0095942632429, + 2104.009603605271, + 2107.0095942632429, + 2107.0095942632429, + 2116.0095662371587, + 2110.0095849212148 + ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2168.9700000000003 ], + "center_y" : [ -924.09283021371505 ], + "center_z" : [ 2104.3846024375175 ], + "charge" : [ 31560.92578125 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 16 ], + "slice_index_max" : [ 7144 ], + "slice_index_min" : [ 7140 ], + "u_wire_index_max" : [ 960 ], + "u_wire_index_min" : [ 952 ], + "v_wire_index_max" : [ 419 ], + "v_wire_index_min" : [ 415 ], + "w_wire_index_max" : [ 705 ], + "w_wire_index_min" : [ 699 ], + "wpid" : [ 7 ] + }, + "t" : [ + 3570000.0, + 3570000.0, + 3570000.0, + 3570000.0, + 3570000.0, + 3570000.0, + 3570000.0, + 3570000.0, + 3570000.0, + 3570000.0, + 3570000.0, + 3570000.0, + 3570000.0, + 3570000.0, + 3570000.0, + 3570000.0 + ], + "ucharge_unc" : [ + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0 + ], + "ucharge_val" : [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + "uwire_index" : [ + 952, + 953, + 954, + 954, + 955, + 955, + 955, + 955, + 956, + 956, + 956, + 957, + 957, + 958, + 958, + 959 + ], + "vcharge_unc" : [ + 1367.0, + 1367.0, + 1367.0, + 1367.0, + 1219.0, + 1552.0, + 1367.0, + 1367.0, + 1219.0, + 1552.0, + 1367.0, + 1219.0, + 1552.0, + 1219.0, + 1367.0, + 1219.0 + ], + "vcharge_val" : [ + 4353.0, + 4353.0, + 8256.0, + 4353.0, + 7845.0, + 10919.0, + 8256.0, + 4353.0, + 7845.0, + 10919.0, + 8256.0, + 7845.0, + 10919.0, + 7845.0, + 4353.0, + 7845.0 + ], + "vwire_index" : [ + 418, + 418, + 417, + 418, + 415, + 416, + 417, + 418, + 415, + 416, + 417, + 415, + 416, + 415, + 418, + 415 + ], + "wcharge_unc" : [ + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 0.0, + 195.0 + ], + "wcharge_val" : [ + 875.0, + 8594.0, + 8594.0, + 15942.0, + 875.0, + 8594.0, + 15942.0, + 6724.0, + 8594.0, + 15942.0, + 6724.0, + 15942.0, + 6724.0, + 6724.0, + 0.0, + 1242.0 + ], + "wpid" : [ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 ], + "wwire_index" : [ + 699, + 700, + 700, + 701, + 699, + 700, + 701, + 702, + 700, + 701, + 702, + 701, + 702, + 702, + 705, + 703 + ], + "x" : [ + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998 + ], + "x_t0cor" : [ + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998, + 2168.9699999999998 + ], + "y" : [ + -915.21606090261776, + -916.94811345112453, + -920.41221854813807, + -918.6801659996313, + -925.60837619365839, + -923.87632364515161, + -922.14427109664484, + -920.41221854813807, + -927.34042874216516, + -925.60837619365839, + -923.87632364515161, + -929.07248129067193, + -927.34042874216516, + -930.8045338391787, + -925.60837619365839, + -932.53658638768547 + ], + "z" : [ + 2098.0096222893271, + 2101.009612947299, + 2101.009612947299, + 2104.009603605271, + 2098.0096222893271, + 2101.009612947299, + 2104.009603605271, + 2107.0095942632429, + 2101.009612947299, + 2104.009603605271, + 2107.0095942632429, + 2104.009603605271, + 2107.0095942632429, + 2107.0095942632429, + 2116.0095662371587, + 2110.0095849212148 + ] + }, + { + "2dp0_x" : [ 2171.172, 2171.172 ], + "2dp0_y" : [ 1835.6018266072961, 1835.6018119456994 ], + "2dp1_x" : [ 2171.172, 2171.172 ], + "2dp1_y" : [ 262.39889706882013, 265.39888966406352 ], + "2dp2_x" : [ 2171.172, 2171.172 ], + "2dp2_y" : [ 2098.0096222893271, 2101.009612947299 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2171.172 ], + "center_y" : [ -907.42182443433705 ], + "center_z" : [ 2099.5096176183133 ], + "charge" : [ 5601.888671875 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 1 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 0 ], + "npoints" : [ 2 ], + "slice_index_max" : [ 7148 ], + "slice_index_min" : [ 7144 ], + "u_wire_index_max" : [ 951 ], + "u_wire_index_min" : [ 950 ], + "v_wire_index_max" : [ 422 ], + "v_wire_index_min" : [ 420 ], + "w_wire_index_max" : [ 701 ], + "w_wire_index_min" : [ 699 ], + "wpid" : [ 7 ] + }, + "t" : [ 3572000.0, 3572000.0 ], + "ucharge_unc" : [ 1081.0, 1081.0 ], + "ucharge_val" : [ 5325.0, 5325.0 ], + "uwire_index" : [ 950, 950 ], + "vcharge_unc" : [ 1299.0, 1492.0 ], + "vcharge_val" : [ 2271.0, 1100.0 ], + "vwire_index" : [ 420, 421 ], + "wcharge_unc" : [ 195.0, 195.0 ], + "wcharge_val" : [ 1712.0, 11593.0 ], + "wpid" : [ 7, 7 ], + "wwire_index" : [ 699, 700 ], + "x" : [ 2171.172, 2171.172 ], + "x_t0cor" : [ 2171.172, 2171.172 ], + "y" : [ -908.28785070859044, -906.55579816008367 ], + "z" : [ 2098.0096222893271, 2101.009612947299 ] + }, + { + "2dp0_x" : [ + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172 + ], + "2dp0_y" : [ + 1838.6018265803343, + 1844.6018264323723, + 1847.6018483508542, + 1847.6018336892118, + 1847.6018190275695, + 1847.6018043659274, + 1850.6018409460523, + 1850.60182628441, + 1853.6018482028921, + 1853.6018335412496, + 1856.6018407980901, + 1856.6018261364479, + 1859.6018333932875 + ], + "2dp1_x" : [ + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172 + ], + "2dp1_y" : [ + 260.88926848509141, + 257.88925590868871, + 251.88924671457153, + 254.88924865184913, + 257.88925058912662, + 260.88925252640422, + 251.88924139500864, + 254.88924333228613, + 248.88923413816894, + 251.88923607544655, + 248.88922881860606, + 251.88923075588355, + 248.88922349904396 + ], + "2dp2_x" : [ + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172 + ], + "2dp2_y" : [ + 2099.5000000000064, + 2102.5000000000064, + 2099.5000000000064, + 2102.5000000000064, + 2105.5000000000064, + 2108.5000000000064, + 2102.5000000000064, + 2105.5000000000064, + 2102.5000000000064, + 2105.5000000000064, + 2105.5000000000064, + 2108.5000000000064, + 2108.5000000000064 + ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2171.1719999999996 ], + "center_y" : [ -921.68349330123408 ], + "center_z" : [ 2104.3461538461606 ], + "charge" : [ 22404.998046875 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 2 ], + "npoints" : [ 13 ], + "slice_index_max" : [ 7148 ], + "slice_index_min" : [ 7144 ], + "u_wire_index_max" : [ 959 ], + "u_wire_index_min" : [ 951 ], + "v_wire_index_max" : [ 422 ], + "v_wire_index_min" : [ 416 ], + "w_wire_index_max" : [ 703 ], + "w_wire_index_min" : [ 699 ], + "wpid" : [ 7 ] + }, + "t" : [ + 3572000.0, + 3572000.0, + 3572000.0, + 3572000.0, + 3572000.0, + 3572000.0, + 3572000.0, + 3572000.0, + 3572000.0, + 3572000.0, + 3572000.0, + 3572000.0, + 3572000.0 + ], + "ucharge_unc" : [ + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0 + ], + "ucharge_val" : [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ], + "uwire_index" : [ 951, 953, 954, 954, 954, 954, 955, 955, 956, 956, 957, 957, 958 ], + "vcharge_unc" : [ + 1299.0, + 1219.0, + 1367.0, + 1367.0, + 1219.0, + 1299.0, + 1367.0, + 1367.0, + 1552.0, + 1367.0, + 1552.0, + 1367.0, + 1552.0 + ], + "vcharge_val" : [ + 2271.0, + 5855.0, + 7587.0, + 8714.0, + 5855.0, + 2271.0, + 7587.0, + 8714.0, + 4235.0, + 7587.0, + 4235.0, + 7587.0, + 4235.0 + ], + "vwire_index" : [ 420, 419, 417, 418, 419, 420, 417, 418, 416, 417, 416, 417, 416 ], + "wcharge_unc" : [ + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0 + ], + "wcharge_val" : [ + 1712.0, + 11593.0, + 1712.0, + 11593.0, + 11939.0, + 2761.0, + 11593.0, + 11939.0, + 11593.0, + 11939.0, + 11939.0, + 2761.0, + 2761.0 + ], + "wpid" : [ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 ], + "wwire_index" : [ 699, 700, 699, 700, 701, 702, 700, 701, 700, 701, 701, 702, 702 ], + "x" : [ + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172 + ], + "x_t0cor" : [ + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172, + 2171.172 + ], + "y" : [ + -910.89148228873353, + -916.0876345406092, + -921.2837975797737, + -919.55173963762229, + -917.81968169547088, + -916.08762375331946, + -923.01584473463629, + -921.28378679248488, + -926.47994983164938, + -924.74789188949796, + -928.21199698651196, + -926.47993904436055, + -929.94404414137364 + ], + "z" : [ + 2099.5000000000064, + 2102.5000000000064, + 2099.5000000000064, + 2102.5000000000064, + 2105.5000000000064, + 2108.5000000000064, + 2102.5000000000064, + 2105.5000000000064, + 2102.5000000000064, + 2105.5000000000064, + 2105.5000000000064, + 2108.5000000000064, + 2108.5000000000064 + ] + }, + { + "2dp0_x" : [ 2171.172, 2171.172, 2171.172 ], + "2dp0_y" : [ 1847.6017750426427, 1856.6017968131632, 1862.6018113268433 ], + "2dp1_x" : [ 2171.172, 2171.172, 2171.172 ], + "2dp1_y" : [ 266.8892564009592, 257.88923463043864, 251.88922011675857 ], + "2dp2_x" : [ 2171.172, 2171.172, 2171.172 ], + "2dp2_y" : [ 2114.5000000000064, 2114.5000000000064, 2114.5000000000064 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2171.172 ], + "center_y" : [ -921.86112146105313 ], + "center_z" : [ 2114.5000000000064 ], + "charge" : [ 390.6064453125 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 2 ], + "npoints" : [ 3 ], + "slice_index_max" : [ 7148 ], + "slice_index_min" : [ 7144 ], + "u_wire_index_max" : [ 960 ], + "u_wire_index_min" : [ 954 ], + "v_wire_index_max" : [ 422 ], + "v_wire_index_min" : [ 416 ], + "w_wire_index_max" : [ 705 ], + "w_wire_index_min" : [ 704 ], + "wpid" : [ 7 ] + }, + "t" : [ 3572000.0, 3572000.0, 3572000.0 ], + "ucharge_unc" : [ 999999995904.0, 999999995904.0, 999999995904.0 ], + "ucharge_val" : [ 0.0, 0.0, 0.0 ], + "uwire_index" : [ 954, 957, 959 ], + "vcharge_unc" : [ 0.0, 1219.0, 1367.0 ], + "vcharge_val" : [ 0.0, 5855.0, 7587.0 ], + "vwire_index" : [ 422, 419, 417 ], + "wcharge_unc" : [ 196.0, 196.0, 196.0 ], + "wcharge_val" : [ 402.0, 402.0, 402.0 ], + "wpid" : [ 7, 7, 7 ], + "wwire_index" : [ 704, 704, 704 ], + "x" : [ 2171.172, 2171.172, 2171.172 ], + "x_t0cor" : [ 2171.172, 2171.172, 2171.172 ], + "y" : [ -912.62350786901663, -923.01582316005772, -929.94403335408481 ], + "z" : [ 2114.5000000000064, 2114.5000000000064, 2114.5000000000064 ] + }, + { + "2dp0_x" : [ 2173.3739999999998, 2173.3739999999998 ], + "2dp0_y" : [ 1835.6018266072961, 1835.6018119456994 ], + "2dp1_x" : [ 2173.3739999999998, 2173.3739999999998 ], + "2dp1_y" : [ 262.39889706882013, 265.39888966406352 ], + "2dp2_x" : [ 2173.3739999999998, 2173.3739999999998 ], + "2dp2_y" : [ 2098.0096222893271, 2101.009612947299 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2173.3739999999998 ], + "center_y" : [ -907.42182443433705 ], + "center_z" : [ 2099.5096176183133 ], + "charge" : [ 7802.400390625 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 1 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 0 ], + "npoints" : [ 2 ], + "slice_index_max" : [ 7152 ], + "slice_index_min" : [ 7148 ], + "u_wire_index_max" : [ 951 ], + "u_wire_index_min" : [ 950 ], + "v_wire_index_max" : [ 422 ], + "v_wire_index_min" : [ 420 ], + "w_wire_index_max" : [ 701 ], + "w_wire_index_min" : [ 699 ], + "wpid" : [ 7 ] + }, + "t" : [ 3574000.0, 3574000.0 ], + "ucharge_unc" : [ 1081.0, 1081.0 ], + "ucharge_val" : [ 7439.0, 7439.0 ], + "uwire_index" : [ 950, 950 ], + "vcharge_unc" : [ 1299.0, 1492.0 ], + "vcharge_val" : [ 6330.0, 3453.0 ], + "vwire_index" : [ 420, 421 ], + "wcharge_unc" : [ 195.0, 195.0 ], + "wcharge_val" : [ 4217.0, 14164.0 ], + "wpid" : [ 7, 7 ], + "wwire_index" : [ 699, 700 ], + "x" : [ 2173.3739999999998, 2173.3739999999998 ], + "x_t0cor" : [ 2173.3739999999998, 2173.3739999999998 ], + "y" : [ -908.28785070859044, -906.55579816008367 ], + "z" : [ 2098.0096222893271, 2101.009612947299 ] + }, + { + "2dp0_x" : [ + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998 + ], + "2dp0_y" : [ + 1838.6018265803343, + 1841.6018338371741, + 1841.6018191755318, + 1844.6018410940146, + 1844.6018264323723, + 1844.6018117707299, + 1847.6018483508542, + 1847.6018336892118, + 1847.6018190275695, + 1847.6018043659274, + 1850.60182628441, + 1856.6018261364479 + ], + "2dp1_x" : [ + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998 + ], + "2dp1_y" : [ + 260.88926848509141, + 257.88926122825171, + 260.8892631655292, + 254.88925397141122, + 257.88925590868871, + 260.88925784596631, + 251.88924671457153, + 254.88924865184913, + 257.88925058912662, + 260.88925252640422, + 254.88924333228613, + 251.88923075588355 + ], + "2dp2_x" : [ + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998 + ], + "2dp2_y" : [ + 2099.5000000000064, + 2099.5000000000064, + 2102.5000000000064, + 2099.5000000000064, + 2102.5000000000064, + 2105.5000000000064, + 2099.5000000000064, + 2102.5000000000064, + 2105.5000000000064, + 2108.5000000000064, + 2105.5000000000064, + 2108.5000000000064 + ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2173.3739999999998 ], + "center_y" : [ -917.38667260357795 ], + "center_z" : [ 2103.2500000000068 ], + "charge" : [ 19805.58203125 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 2 ], + "npoints" : [ 12 ], + "slice_index_max" : [ 7152 ], + "slice_index_min" : [ 7148 ], + "u_wire_index_max" : [ 958 ], + "u_wire_index_min" : [ 951 ], + "v_wire_index_max" : [ 422 ], + "v_wire_index_min" : [ 417 ], + "w_wire_index_max" : [ 703 ], + "w_wire_index_min" : [ 699 ], + "wpid" : [ 7 ] + }, + "t" : [ + 3574000.0, + 3574000.0, + 3574000.0, + 3574000.0, + 3574000.0, + 3574000.0, + 3574000.0, + 3574000.0, + 3574000.0, + 3574000.0, + 3574000.0, + 3574000.0 + ], + "ucharge_unc" : [ + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0, + 999999995904.0 + ], + "ucharge_val" : [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ], + "uwire_index" : [ 951, 952, 952, 953, 953, 953, 954, 954, 954, 954, 955, 957 ], + "vcharge_unc" : [ + 1299.0, + 1219.0, + 1299.0, + 1367.0, + 1219.0, + 1299.0, + 1367.0, + 1367.0, + 1219.0, + 1299.0, + 1367.0, + 1367.0 + ], + "vcharge_val" : [ + 6330.0, + 10308.0, + 6330.0, + 7741.0, + 10308.0, + 6330.0, + 2359.0, + 7741.0, + 10308.0, + 6330.0, + 7741.0, + 2359.0 + ], + "vwire_index" : [ 420, 419, 420, 418, 419, 420, 417, 418, 419, 420, 418, 417 ], + "wcharge_unc" : [ + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0 + ], + "wcharge_val" : [ + 4217.0, + 4217.0, + 14164.0, + 4217.0, + 14164.0, + 8303.0, + 4217.0, + 14164.0, + 8303.0, + 897.0, + 8303.0, + 897.0 + ], + "wpid" : [ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 ], + "wwire_index" : [ 699, 699, 700, 699, 700, 701, 699, 700, 701, 702, 701, 702 ], + "x" : [ + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998 + ], + "x_t0cor" : [ + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998 + ], + "y" : [ + -910.89148228873353, + -914.35558738574662, + -912.6235294435952, + -917.81969248276062, + -916.0876345406092, + -914.35557659845779, + -921.2837975797737, + -919.55173963762229, + -917.81968169547088, + -916.08762375331946, + -921.28378679248488, + -926.47993904436055 + ], + "z" : [ + 2099.5000000000064, + 2099.5000000000064, + 2102.5000000000064, + 2099.5000000000064, + 2102.5000000000064, + 2105.5000000000064, + 2099.5000000000064, + 2102.5000000000064, + 2105.5000000000064, + 2108.5000000000064, + 2105.5000000000064, + 2108.5000000000064 + ] + }, + { + "2dp0_x" : [ 2175.576, 2175.576, 2175.576, 2175.576, 2175.576, 2175.576, 2175.576 ], + "2dp0_y" : [ + 1835.6018412688927, + 1835.6018266072961, + 1832.601819350456, + 1835.6018119456994, + 1829.601812093616, + 1832.6018046888594, + 1835.601797284103 + ], + "2dp1_x" : [ 2175.576, 2175.576, 2175.576, 2175.576, 2175.576, 2175.576, 2175.576 ], + "2dp1_y" : [ + 259.39890447357675, + 262.39889706882013, + 265.39890432566017, + 265.39888966406352, + 268.3989115825002, + 268.39889692090355, + 268.39888225930713 + ], + "2dp2_x" : [ 2175.576, 2175.576, 2175.576, 2175.576, 2175.576, 2175.576, 2175.576 ], + "2dp2_y" : [ + 2095.0096316313552, + 2098.0096222893271, + 2098.0096222893271, + 2101.009612947299, + 2098.0096222893271, + 2101.009612947299, + 2104.009603605271 + ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2175.5760000000005 ], + "center_y" : [ -905.56605384665113 ], + "center_z" : [ 2099.2953325713147 ], + "charge" : [ 19094.71484375 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 1 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 0 ], + "npoints" : [ 7 ], + "slice_index_max" : [ 7156 ], + "slice_index_min" : [ 7152 ], + "u_wire_index_max" : [ 951 ], + "u_wire_index_min" : [ 948 ], + "v_wire_index_max" : [ 423 ], + "v_wire_index_min" : [ 419 ], + "w_wire_index_max" : [ 702 ], + "w_wire_index_min" : [ 698 ], + "wpid" : [ 7 ] + }, + "t" : [ + 3576000.0, + 3576000.0, + 3576000.0, + 3576000.0, + 3576000.0, + 3576000.0, + 3576000.0 + ], + "ucharge_unc" : [ 1081.0, 1081.0, 898.0, 1081.0, 932.0, 898.0, 1081.0 ], + "ucharge_val" : [ 11599.0, 11599.0, 4408.0, 11599.0, 2181.0, 4408.0, 11599.0 ], + "uwire_index" : [ 950, 950, 949, 950, 948, 949, 950 ], + "vcharge_unc" : [ 1219.0, 1299.0, 1492.0, 1492.0, 1299.0, 1299.0, 1299.0 ], + "vcharge_val" : [ 7404.0, 9215.0, 8138.0, 8138.0, 4757.0, 4757.0, 4757.0 ], + "vwire_index" : [ 419, 420, 421, 421, 422, 422, 422 ], + "wcharge_unc" : [ 196.0, 195.0, 195.0, 195.0, 195.0, 195.0, 195.0 ], + "wcharge_val" : [ 530.0, 8711.0, 8711.0, 15746.0, 8711.0, 15746.0, 5212.0 ], + "wpid" : [ 7, 7, 7, 7, 7, 7, 7 ], + "wwire_index" : [ 698, 699, 699, 700, 699, 700, 701 ], + "x" : [ 2175.576, 2175.576, 2175.576, 2175.576, 2175.576, 2175.576, 2175.576 ], + "x_t0cor" : [ 2175.576, 2175.576, 2175.576, 2175.576, 2175.576, 2175.576, 2175.576 ], + "y" : [ + -910.01990325709721, + -908.28785070859044, + -904.8237456115769, + -906.55579816008367, + -901.35964051456335, + -903.09169306307012, + -904.8237456115769 + ], + "z" : [ + 2095.0096316313552, + 2098.0096222893271, + 2098.0096222893271, + 2101.009612947299, + 2098.0096222893271, + 2101.009612947299, + 2104.009603605271 + ] + }, + { + "2dp0_x" : [ 2177.7779999999998 ], + "2dp0_y" : [ 1820.6018049846925 ], + "2dp1_x" : [ 2177.7779999999998 ], + "2dp1_y" : [ 274.39894075777681 ], + "2dp2_x" : [ 2177.7779999999998 ], + "2dp2_y" : [ 2095.0096316313552 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2177.7779999999998 ], + "center_y" : [ -892.69937777202972 ], + "center_z" : [ 2095.0096316313552 ], + "charge" : [ 1378.940673828125 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 1 ], + "slice_index_max" : [ 7160 ], + "slice_index_min" : [ 7156 ], + "u_wire_index_max" : [ 946 ], + "u_wire_index_min" : [ 945 ], + "v_wire_index_max" : [ 425 ], + "v_wire_index_min" : [ 424 ], + "w_wire_index_max" : [ 699 ], + "w_wire_index_min" : [ 698 ], + "wpid" : [ 7 ] + }, + "t" : [ 3578000.0 ], + "ucharge_unc" : [ 971.0 ], + "ucharge_val" : [ 1577.0 ], + "uwire_index" : [ 945 ], + "vcharge_unc" : [ 1367.0 ], + "vcharge_val" : [ 1721.0 ], + "vwire_index" : [ 424 ], + "wcharge_unc" : [ 196.0 ], + "wcharge_val" : [ 2117.0 ], + "wpid" : [ 7 ], + "wwire_index" : [ 698 ], + "x" : [ 2177.7779999999998 ], + "x_t0cor" : [ 2177.7779999999998 ], + "y" : [ -892.69937777202972 ], + "z" : [ 2095.0096316313552 ] + }, + { + "2dp0_x" : [ + 2177.7779999999998, + 2177.7779999999998, + 2177.7779999999998, + 2177.7779999999998, + 2177.7779999999998, + 2177.7779999999998, + 2177.7779999999998, + 2177.7779999999998, + 2177.7779999999998 + ], + "2dp0_y" : [ + 1835.6018266072961, + 1832.601819350456, + 1835.6018119456994, + 1829.601812093616, + 1832.6018046888594, + 1826.601804836776, + 1835.6017826225063, + 1826.6017901751793, + 1835.6017679609097 + ], + "2dp1_x" : [ + 2177.7779999999998, + 2177.7779999999998, + 2177.7779999999998, + 2177.7779999999998, + 2177.7779999999998, + 2177.7779999999998, + 2177.7779999999998, + 2177.7779999999998, + 2177.7779999999998 + ], + "2dp1_y" : [ + 262.39889706882013, + 265.39890432566017, + 265.39888966406352, + 268.3989115825002, + 268.39889692090355, + 271.39891883934024, + 271.39887485455051, + 274.39891143458374, + 274.3988674497939 + ], + "2dp2_x" : [ + 2177.7779999999998, + 2177.7779999999998, + 2177.7779999999998, + 2177.7779999999998, + 2177.7779999999998, + 2177.7779999999998, + 2177.7779999999998, + 2177.7779999999998, + 2177.7779999999998 + ], + "2dp2_y" : [ + 2098.0096222893271, + 2098.0096222893271, + 2101.009612947299, + 2098.0096222893271, + 2101.009612947299, + 2098.0096222893271, + 2107.0095942632429, + 2101.009612947299, + 2110.0095849212148 + ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2177.7779999999998 ], + "center_y" : [ -902.51434221356783 ], + "center_z" : [ 2101.3429452426294 ], + "charge" : [ 31461.185546875 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 1 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 0 ], + "npoints" : [ 9 ], + "slice_index_max" : [ 7160 ], + "slice_index_min" : [ 7156 ], + "u_wire_index_max" : [ 951 ], + "u_wire_index_min" : [ 947 ], + "v_wire_index_max" : [ 425 ], + "v_wire_index_min" : [ 420 ], + "w_wire_index_max" : [ 703 ], + "w_wire_index_min" : [ 698 ], + "wpid" : [ 7 ] + }, + "t" : [ + 3578000.0, + 3578000.0, + 3578000.0, + 3578000.0, + 3578000.0, + 3578000.0, + 3578000.0, + 3578000.0, + 3578000.0 + ], + "ucharge_unc" : [ 1081.0, 898.0, 1081.0, 932.0, 898.0, 932.0, 1081.0, 932.0, 1081.0 ], + "ucharge_val" : [ + 12323.0, + 8779.0, + 12323.0, + 5993.0, + 8779.0, + 3380.0, + 12323.0, + 3380.0, + 12323.0 + ], + "uwire_index" : [ 950, 949, 950, 948, 949, 947, 950, 947, 950 ], + "vcharge_unc" : [ + 1299.0, + 1492.0, + 1492.0, + 1299.0, + 1299.0, + 1299.0, + 1299.0, + 1367.0, + 1367.0 + ], + "vcharge_val" : [ + 6679.0, + 10955.0, + 10955.0, + 12518.0, + 12518.0, + 3898.0, + 3898.0, + 1721.0, + 1721.0 + ], + "vwire_index" : [ 420, 421, 421, 422, 422, 423, 423, 424, 424 ], + "wcharge_unc" : [ 195.0, 195.0, 195.0, 195.0, 195.0, 195.0, 195.0, 195.0, 0.0 ], + "wcharge_val" : [ + 13518.0, + 13518.0, + 14974.0, + 13518.0, + 14974.0, + 13518.0, + 874.0, + 14974.0, + 0.0 + ], + "wpid" : [ 7, 7, 7, 7, 7, 7, 7, 7, 7 ], + "wwire_index" : [ 699, 699, 700, 699, 700, 699, 702, 700, 703 ], + "x" : [ + 2177.7779999999998, + 2177.7779999999998, + 2177.7779999999998, + 2177.7779999999998, + 2177.7779999999998, + 2177.7779999999998, + 2177.7779999999998, + 2177.7779999999998, + 2177.7779999999998 + ], + "x_t0cor" : [ + 2177.7779999999998, + 2177.7779999999998, + 2177.7779999999998, + 2177.7779999999998, + 2177.7779999999998, + 2177.7779999999998, + 2177.7779999999998, + 2177.7779999999998, + 2177.7779999999998 + ], + "y" : [ + -908.28785070859044, + -904.8237456115769, + -906.55579816008367, + -901.35964051456335, + -903.09169306307012, + -897.89553541754981, + -903.09169306307012, + -896.16348286904304, + -901.35964051456335 + ], + "z" : [ + 2098.0096222893271, + 2098.0096222893271, + 2101.009612947299, + 2098.0096222893271, + 2101.009612947299, + 2098.0096222893271, + 2107.0095942632429, + 2101.009612947299, + 2110.0095849212148 + ] + }, + { + "2dp0_x" : [ 2177.7779999999998, 2177.7779999999998 ], + "2dp0_y" : [ 1823.6018122415326, 1823.6017975799359 ], + "2dp1_x" : [ 2177.7779999999998, 2177.7779999999998 ], + "2dp1_y" : [ 271.39893350093701, 274.39892609618039 ], + "2dp2_x" : [ 2177.7779999999998, 2177.7779999999998 ], + "2dp2_y" : [ 2095.0096316313552, 2098.0096222893271 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2177.7779999999998 ], + "center_y" : [ -895.29745659478965 ], + "center_z" : [ 2096.5096269603409 ], + "charge" : [ 1702.291259765625 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 1 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 0 ], + "npoints" : [ 2 ], + "slice_index_max" : [ 7160 ], + "slice_index_min" : [ 7156 ], + "u_wire_index_max" : [ 947 ], + "u_wire_index_min" : [ 946 ], + "v_wire_index_max" : [ 425 ], + "v_wire_index_min" : [ 423 ], + "w_wire_index_max" : [ 700 ], + "w_wire_index_min" : [ 698 ], + "wpid" : [ 7 ] + }, + "t" : [ 3578000.0, 3578000.0 ], + "ucharge_unc" : [ 999999995904.0, 999999995904.0 ], + "ucharge_val" : [ 0.0, 0.0 ], + "uwire_index" : [ 946, 946 ], + "vcharge_unc" : [ 1299.0, 1367.0 ], + "vcharge_val" : [ 3898.0, 1721.0 ], + "vwire_index" : [ 423, 424 ], + "wcharge_unc" : [ 196.0, 195.0 ], + "wcharge_val" : [ 2117.0, 13518.0 ], + "wpid" : [ 7, 7 ], + "wwire_index" : [ 698, 699 ], + "x" : [ 2177.7779999999998, 2177.7779999999998 ], + "x_t0cor" : [ 2177.7779999999998, 2177.7779999999998 ], + "y" : [ -896.16348286904304, -894.43143032053626 ], + "z" : [ 2095.0096316313552, 2098.0096222893271 ] + }, + { + "2dp0_x" : [ 2177.7779999999998, 2177.7779999999998, 2177.7779999999998 ], + "2dp0_y" : [ 1838.601848525733, 1847.6018263114634, 1853.6018115019501 ], + "2dp1_x" : [ 2177.7779999999998, 2177.7779999999998, 2177.7779999999998 ], + "2dp1_y" : [ 256.39889721673649, 256.39885323194665, 256.39882390875334 ], + "2dp2_x" : [ 2177.7779999999998, 2177.7779999999998, 2177.7779999999998 ], + "2dp2_y" : [ 2095.0096316313552, 2104.009603605271, 2110.0095849212148 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2177.7779999999998 ], + "center_y" : [ -918.102815150129 ], + "center_z" : [ 2103.0096067192803 ], + "charge" : [ 554.95135498046875 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 3 ], + "slice_index_max" : [ 7160 ], + "slice_index_min" : [ 7156 ], + "u_wire_index_max" : [ 957 ], + "u_wire_index_min" : [ 951 ], + "v_wire_index_max" : [ 419 ], + "v_wire_index_min" : [ 418 ], + "w_wire_index_max" : [ 703 ], + "w_wire_index_min" : [ 698 ], + "wpid" : [ 7 ] + }, + "t" : [ 3578000.0, 3578000.0, 3578000.0 ], + "ucharge_unc" : [ 999999995904.0, 999999995904.0, 999999995904.0 ], + "ucharge_val" : [ 0.0, 0.0, 0.0 ], + "uwire_index" : [ 951, 954, 956 ], + "vcharge_unc" : [ 1367.0, 1367.0, 1367.0 ], + "vcharge_val" : [ 886.0, 886.0, 886.0 ], + "vwire_index" : [ 418, 418, 418 ], + "wcharge_unc" : [ 196.0, 195.0, 0.0 ], + "wcharge_val" : [ 2117.0, 3593.0, 0.0 ], + "wpid" : [ 7, 7, 7 ], + "wwire_index" : [ 698, 701, 703 ], + "x" : [ 2177.7779999999998, 2177.7779999999998, 2177.7779999999998 ], + "x_t0cor" : [ 2177.7779999999998, 2177.7779999999998, 2177.7779999999998 ], + "y" : [ -913.48400835411098, -918.6801659996313, -922.14427109664484 ], + "z" : [ 2095.0096316313552, 2104.009603605271, 2110.0095849212148 ] + }, + { + "2dp0_x" : [ 2179.98 ], + "2dp0_y" : [ 1820.6018049846925 ], + "2dp1_x" : [ 2179.98 ], + "2dp1_y" : [ 274.39894075777681 ], + "2dp2_x" : [ 2179.98 ], + "2dp2_y" : [ 2095.0096316313552 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2179.98 ], + "center_y" : [ -892.69937777202972 ], + "center_z" : [ 2095.0096316313552 ], + "charge" : [ 3727.279296875 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 1 ], + "slice_index_max" : [ 7164 ], + "slice_index_min" : [ 7160 ], + "u_wire_index_max" : [ 946 ], + "u_wire_index_min" : [ 945 ], + "v_wire_index_max" : [ 425 ], + "v_wire_index_min" : [ 424 ], + "w_wire_index_max" : [ 699 ], + "w_wire_index_min" : [ 698 ], + "wpid" : [ 7 ] + }, + "t" : [ 3580000.0 ], + "ucharge_unc" : [ 971.0 ], + "ucharge_val" : [ 3519.0 ], + "uwire_index" : [ 945 ], + "vcharge_unc" : [ 1367.0 ], + "vcharge_val" : [ 5299.0 ], + "vwire_index" : [ 424 ], + "wcharge_unc" : [ 196.0 ], + "wcharge_val" : [ 5132.0 ], + "wpid" : [ 7 ], + "wwire_index" : [ 698 ], + "x" : [ 2179.98 ], + "x_t0cor" : [ 2179.98 ], + "y" : [ -892.69937777202972 ], + "z" : [ 2095.0096316313552 ] + }, + { + "2dp0_x" : [ + 2179.98, + 2179.98, + 2179.98, + 2179.98, + 2179.98, + 2179.98, + 2179.98, + 2179.98, + 2179.98, + 2179.98, + 2179.98, + 2179.98 + ], + "2dp0_y" : [ + 1835.6018266072961, + 1829.6018267552126, + 1832.601819350456, + 1835.6018119456994, + 1826.6018194983726, + 1829.601812093616, + 1832.6018046888594, + 1826.601804836776, + 1829.6017974320193, + 1835.6017826225063, + 1826.6017901751793, + 1835.6017679609097 + ], + "2dp1_x" : [ + 2179.98, + 2179.98, + 2179.98, + 2179.98, + 2179.98, + 2179.98, + 2179.98, + 2179.98, + 2179.98, + 2179.98, + 2179.98, + 2179.98 + ], + "2dp1_y" : [ + 262.39889706882013, + 265.39891898725682, + 265.39890432566017, + 265.39888966406352, + 268.39892624409686, + 268.3989115825002, + 268.39889692090355, + 271.39891883934024, + 271.39890417774359, + 271.39887485455051, + 274.39891143458374, + 274.3988674497939 + ], + "2dp2_x" : [ + 2179.98, + 2179.98, + 2179.98, + 2179.98, + 2179.98, + 2179.98, + 2179.98, + 2179.98, + 2179.98, + 2179.98, + 2179.98, + 2179.98 + ], + "2dp2_y" : [ + 2098.0096222893271, + 2095.0096316313552, + 2098.0096222893271, + 2101.009612947299, + 2095.0096316313552, + 2098.0096222893271, + 2101.009612947299, + 2098.0096222893271, + 2101.009612947299, + 2107.0095942632429, + 2101.009612947299, + 2110.0095849212148 + ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2179.98 ], + "center_y" : [ -902.08132907644131 ], + "center_z" : [ 2100.259615282806 ], + "charge" : [ 31251.73828125 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 1 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 0 ], + "npoints" : [ 12 ], + "slice_index_max" : [ 7164 ], + "slice_index_min" : [ 7160 ], + "u_wire_index_max" : [ 951 ], + "u_wire_index_min" : [ 947 ], + "v_wire_index_max" : [ 425 ], + "v_wire_index_min" : [ 420 ], + "w_wire_index_max" : [ 703 ], + "w_wire_index_min" : [ 698 ], + "wpid" : [ 7 ] + }, + "t" : [ + 3580000.0, + 3580000.0, + 3580000.0, + 3580000.0, + 3580000.0, + 3580000.0, + 3580000.0, + 3580000.0, + 3580000.0, + 3580000.0, + 3580000.0, + 3580000.0 + ], + "ucharge_unc" : [ + 1081.0, + 932.0, + 898.0, + 1081.0, + 932.0, + 932.0, + 898.0, + 932.0, + 932.0, + 1081.0, + 932.0, + 1081.0 + ], + "ucharge_val" : [ + 6325.0, + 8360.0, + 7359.0, + 6325.0, + 9175.0, + 8360.0, + 7359.0, + 9175.0, + 8360.0, + 6325.0, + 9175.0, + 6325.0 + ], + "uwire_index" : [ 950, 948, 949, 950, 947, 948, 949, 947, 948, 950, 947, 950 ], + "vcharge_unc" : [ + 1299.0, + 1492.0, + 1492.0, + 1492.0, + 1299.0, + 1299.0, + 1299.0, + 1299.0, + 1299.0, + 1299.0, + 1367.0, + 1367.0 + ], + "vcharge_val" : [ + 1417.0, + 7043.0, + 7043.0, + 7043.0, + 12950.0, + 12950.0, + 12950.0, + 8822.0, + 8822.0, + 8822.0, + 5299.0, + 5299.0 + ], + "vwire_index" : [ 420, 421, 421, 421, 422, 422, 422, 423, 423, 423, 424, 424 ], + "wcharge_unc" : [ + 195.0, + 196.0, + 195.0, + 195.0, + 196.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 195.0, + 0.0 + ], + "wcharge_val" : [ + 16160.0, + 5132.0, + 16160.0, + 10816.0, + 5132.0, + 16160.0, + 10816.0, + 16160.0, + 10816.0, + 878.0, + 10816.0, + 0.0 + ], + "wpid" : [ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 ], + "wwire_index" : [ 699, 698, 699, 700, 698, 699, 700, 699, 700, 702, 700, 703 ], + "x" : [ + 2179.98, + 2179.98, + 2179.98, + 2179.98, + 2179.98, + 2179.98, + 2179.98, + 2179.98, + 2179.98, + 2179.98, + 2179.98, + 2179.98 + ], + "x_t0cor" : [ + 2179.98, + 2179.98, + 2179.98, + 2179.98, + 2179.98, + 2179.98, + 2179.98, + 2179.98, + 2179.98, + 2179.98, + 2179.98, + 2179.98 + ], + "y" : [ + -908.28785070859044, + -903.09169306307012, + -904.8237456115769, + -906.55579816008367, + -899.62758796605658, + -901.35964051456335, + -903.09169306307012, + -897.89553541754981, + -899.62758796605658, + -903.09169306307012, + -896.16348286904304, + -901.35964051456335 + ], + "z" : [ + 2098.0096222893271, + 2095.0096316313552, + 2098.0096222893271, + 2101.009612947299, + 2095.0096316313552, + 2098.0096222893271, + 2101.009612947299, + 2098.0096222893271, + 2101.009612947299, + 2107.0095942632429, + 2101.009612947299, + 2110.0095849212148 + ] + }, + { + "2dp0_x" : [ 2179.98, 2179.98 ], + "2dp0_y" : [ 1823.6018122415326, 1823.6017975799359 ], + "2dp1_x" : [ 2179.98, 2179.98 ], + "2dp1_y" : [ 271.39893350093701, 274.39892609618039 ], + "2dp2_x" : [ 2179.98, 2179.98 ], + "2dp2_y" : [ 2095.0096316313552, 2098.0096222893271 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2179.98 ], + "center_y" : [ -895.29745659478965 ], + "center_z" : [ 2096.5096269603409 ], + "charge" : [ 2.1986382007598877 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 1 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 0 ], + "npoints" : [ 2 ], + "slice_index_max" : [ 7164 ], + "slice_index_min" : [ 7160 ], + "u_wire_index_max" : [ 947 ], + "u_wire_index_min" : [ 946 ], + "v_wire_index_max" : [ 425 ], + "v_wire_index_min" : [ 423 ], + "w_wire_index_max" : [ 700 ], + "w_wire_index_min" : [ 698 ], + "wpid" : [ 7 ] + }, + "t" : [ 3580000.0, 3580000.0 ], + "ucharge_unc" : [ 999999995904.0, 999999995904.0 ], + "ucharge_val" : [ 0.0, 0.0 ], + "uwire_index" : [ 946, 946 ], + "vcharge_unc" : [ 1299.0, 1367.0 ], + "vcharge_val" : [ 8822.0, 5299.0 ], + "vwire_index" : [ 423, 424 ], + "wcharge_unc" : [ 196.0, 195.0 ], + "wcharge_val" : [ 5132.0, 16160.0 ], + "wpid" : [ 7, 7 ], + "wwire_index" : [ 698, 699 ], + "x" : [ 2179.98, 2179.98 ], + "x_t0cor" : [ 2179.98, 2179.98 ], + "y" : [ -896.16348286904304, -894.43143032053626 ], + "z" : [ 2095.0096316313552, 2098.0096222893271 ] + }, + { + "2dp0_x" : [ + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998 + ], + "2dp0_y" : [ + 1814.6018051326091, + 1820.6018196462892, + 1820.6018049846925, + 1820.6017903230959 + ], + "2dp1_x" : [ + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998 + ], + "2dp1_y" : [ + 277.39896267621361, + 271.39894816253343, + 274.39894075777681, + 277.3989333530202 + ], + "2dp2_x" : [ + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998 + ], + "2dp2_y" : [ + 2092.0096409733833, + 2092.0096409733833, + 2095.0096316313552, + 2098.0096222893271 + ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2182.1819999999998 ], + "center_y" : [ -891.4003383606497 ], + "center_z" : [ 2094.2596339668626 ], + "charge" : [ 10578.31640625 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 4 ], + "slice_index_max" : [ 7168 ], + "slice_index_min" : [ 7164 ], + "u_wire_index_max" : [ 946 ], + "u_wire_index_min" : [ 943 ], + "v_wire_index_max" : [ 426 ], + "v_wire_index_min" : [ 423 ], + "w_wire_index_max" : [ 700 ], + "w_wire_index_min" : [ 697 ], + "wpid" : [ 7 ] + }, + "t" : [ 3582000.0, 3582000.0, 3582000.0, 3582000.0 ], + "ucharge_unc" : [ 1046.0, 971.0, 971.0, 971.0 ], + "ucharge_val" : [ 510.0, 7271.0, 7271.0, 7271.0 ], + "uwire_index" : [ 943, 945, 945, 945 ], + "vcharge_unc" : [ 1299.0, 1299.0, 1367.0, 1299.0 ], + "vcharge_val" : [ 5361.0, 8921.0, 10325.0, 5361.0 ], + "vwire_index" : [ 425, 423, 424, 425 ], + "wcharge_unc" : [ 195.0, 195.0, 196.0, 195.0 ], + "wcharge_val" : [ 861.0, 861.0, 9300.0, 15557.0 ], + "wpid" : [ 7, 7, 7, 7 ], + "wwire_index" : [ 697, 697, 698, 699 ], + "x" : [ + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998 + ], + "x_t0cor" : [ + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998 + ], + "y" : [ + -887.5032201265094, + -894.43143032053649, + -892.69937777202972, + -890.96732522352295 + ], + "z" : [ + 2092.0096409733833, + 2092.0096409733833, + 2095.0096316313552, + 2098.0096222893271 + ] + }, + { + "2dp0_x" : [ 2182.1819999999998, 2182.1819999999998 ], + "2dp0_y" : [ 1813.0921472254959, 1822.0921530373289 ], + "2dp1_x" : [ 2182.1819999999998, 2182.1819999999998 ], + "2dp1_y" : [ 286.39894783992293, 286.39890385499575 ], + "2dp2_x" : [ 2182.1819999999998, 2182.1819999999998 ], + "2dp2_y" : [ 2099.4999999999995, 2108.4999999999995 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2182.1819999999998 ], + "center_y" : [ -884.03357036236332 ], + "center_z" : [ 2103.9999999999995 ], + "charge" : [ 293.33929443359375 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 2 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 2 ], + "slice_index_max" : [ 7168 ], + "slice_index_min" : [ 7164 ], + "u_wire_index_max" : [ 946 ], + "u_wire_index_min" : [ 943 ], + "v_wire_index_max" : [ 429 ], + "v_wire_index_min" : [ 428 ], + "w_wire_index_max" : [ 703 ], + "w_wire_index_min" : [ 699 ], + "wpid" : [ 7 ] + }, + "t" : [ 3582000.0, 3582000.0 ], + "ucharge_unc" : [ 1046.0, 999999995904.0 ], + "ucharge_val" : [ 510.0, 0.0 ], + "uwire_index" : [ 943, 946 ], + "vcharge_unc" : [ 1552.0, 1552.0 ], + "vcharge_val" : [ 784.0, 784.0 ], + "vwire_index" : [ 428, 428 ], + "wcharge_unc" : [ 195.0, 195.0 ], + "wcharge_val" : [ 15557.0, 633.0 ], + "wpid" : [ 7, 7 ], + "wwire_index" : [ 699, 702 ], + "x" : [ 2182.1819999999998, 2182.1819999999998 ], + "x_t0cor" : [ 2182.1819999999998, 2182.1819999999998 ], + "y" : [ -881.43548344913609, -886.63165727559056 ], + "z" : [ 2099.4999999999995, 2108.4999999999995 ] + }, + { + "2dp0_x" : [ 2182.1819999999998 ], + "2dp0_y" : [ 1832.6018486736496 ], + "2dp1_x" : [ 2182.1819999999998 ], + "2dp1_y" : [ 259.39891913517317 ], + "2dp2_x" : [ 2182.1819999999998 ], + "2dp2_y" : [ 2092.0096409733833 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2182.1819999999998 ], + "center_y" : [ -908.28785070859067 ], + "center_z" : [ 2092.0096409733833 ], + "charge" : [ 1.0 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 1 ], + "slice_index_max" : [ 7168 ], + "slice_index_min" : [ 7164 ], + "u_wire_index_max" : [ 950 ], + "u_wire_index_min" : [ 949 ], + "v_wire_index_max" : [ 420 ], + "v_wire_index_min" : [ 419 ], + "w_wire_index_max" : [ 698 ], + "w_wire_index_min" : [ 697 ], + "wpid" : [ 7 ] + }, + "t" : [ 3582000.0 ], + "ucharge_unc" : [ 898.0 ], + "ucharge_val" : [ 2036.0 ], + "uwire_index" : [ 949 ], + "vcharge_unc" : [ 918.0 ], + "vcharge_val" : [ 150.0 ], + "vwire_index" : [ 419 ], + "wcharge_unc" : [ 195.0 ], + "wcharge_val" : [ 861.0 ], + "wpid" : [ 7 ], + "wwire_index" : [ 697 ], + "x" : [ 2182.1819999999998 ], + "x_t0cor" : [ 2182.1819999999998 ], + "y" : [ -908.28785070859067 ], + "z" : [ 2092.0096409733833 ] + }, + { + "2dp0_x" : [ + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998 + ], + "2dp0_y" : [ + 1826.6018268762587, + 1826.6018122146161, + 1829.6018194714561, + 1826.6017975529735, + 1829.6018048098135, + 1826.6017828913314, + 1829.6017901481714, + 1832.6017974050114, + 1832.601768081727 + ], + "2dp1_x" : [ + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998 + ], + "2dp1_y" : [ + 266.88929363789669, + 269.88929557517451, + 266.88928831833437, + 272.88929751245223, + 269.8892902556122, + 275.88929944972949, + 272.88929219288946, + 269.88928493604942, + 275.88928881060451 + ], + "2dp2_x" : [ + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998 + ], + "2dp2_y" : [ + 2093.5000000000064, + 2096.5000000000064, + 2096.5000000000064, + 2099.5000000000064, + 2099.5000000000064, + 2102.5000000000064, + 2102.5000000000064, + 2102.5000000000064, + 2108.5000000000064 + ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2182.1820000000002 ], + "center_y" : [ -899.34446410010025 ], + "center_z" : [ 2100.1666666666733 ], + "charge" : [ 18953.544921875 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 2 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 0 ], + "npoints" : [ 9 ], + "slice_index_max" : [ 7168 ], + "slice_index_min" : [ 7164 ], + "u_wire_index_max" : [ 950 ], + "u_wire_index_min" : [ 947 ], + "v_wire_index_max" : [ 426 ], + "v_wire_index_min" : [ 421 ], + "w_wire_index_max" : [ 703 ], + "w_wire_index_min" : [ 697 ], + "wpid" : [ 7 ] + }, + "t" : [ + 3582000.0, + 3582000.0, + 3582000.0, + 3582000.0, + 3582000.0, + 3582000.0, + 3582000.0, + 3582000.0, + 3582000.0 + ], + "ucharge_unc" : [ 932.0, 932.0, 932.0, 932.0, 932.0, 932.0, 932.0, 898.0, 898.0 ], + "ucharge_val" : [ + 11216.0, + 11216.0, + 5131.0, + 11216.0, + 5131.0, + 11216.0, + 5131.0, + 2036.0, + 2036.0 + ], + "uwire_index" : [ 947, 947, 948, 947, 948, 947, 948, 949, 949 ], + "vcharge_unc" : [ + 1299.0, + 1299.0, + 1299.0, + 1367.0, + 1299.0, + 1299.0, + 1367.0, + 1299.0, + 1299.0 + ], + "vcharge_val" : [ + 5690.0, + 8921.0, + 5690.0, + 10325.0, + 8921.0, + 5361.0, + 10325.0, + 8921.0, + 5361.0 + ], + "vwire_index" : [ 422, 423, 422, 424, 423, 425, 424, 423, 425 ], + "wcharge_unc" : [ 195.0, 196.0, 196.0, 195.0, 195.0, 195.0, 195.0, 195.0, 195.0 ], + "wcharge_val" : [ + 861.0, + 9300.0, + 9300.0, + 15557.0, + 15557.0, + 5613.0, + 5613.0, + 5613.0, + 633.0 + ], + "wpid" : [ 7, 7, 7, 7, 7, 7, 7, 7, 7 ], + "wwire_index" : [ 697, 698, 698, 699, 699, 700, 700, 700, 702 ], + "x" : [ + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998 + ], + "x_t0cor" : [ + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998 + ], + "y" : [ + -900.49917778498195, + -898.76711984283031, + -902.23122493984386, + -897.03506190067867, + -900.49916699769221, + -895.30300395852748, + -898.76710905554103, + -902.23121415255457, + -898.76709826825174 + ], + "z" : [ + 2093.5000000000064, + 2096.5000000000064, + 2096.5000000000064, + 2099.5000000000064, + 2099.5000000000064, + 2102.5000000000064, + 2102.5000000000064, + 2102.5000000000064, + 2108.5000000000064 + ] + }, + { + "2dp0_x" : [ + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998 + ], + "2dp0_y" : [ + 1823.6018269031292, + 1823.6018122415326, + 1823.6017975799359, + 1823.6017829183393 + ], + "2dp1_x" : [ + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998 + ], + "2dp1_y" : [ + 268.39894090569351, + 271.39893350093701, + 274.39892609618039, + 277.39891869142377 + ], + "2dp2_x" : [ + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998 + ], + "2dp2_y" : [ + 2092.0096409733833, + 2095.0096316313552, + 2098.0096222893271, + 2101.009612947299 + ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2182.1819999999998 ], + "center_y" : [ -895.29745659478965 ], + "center_z" : [ 2096.5096269603414 ], + "charge" : [ 2837.446044921875 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 1 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 0 ], + "npoints" : [ 4 ], + "slice_index_max" : [ 7168 ], + "slice_index_min" : [ 7164 ], + "u_wire_index_max" : [ 947 ], + "u_wire_index_min" : [ 946 ], + "v_wire_index_max" : [ 426 ], + "v_wire_index_min" : [ 422 ], + "w_wire_index_max" : [ 701 ], + "w_wire_index_min" : [ 697 ], + "wpid" : [ 7 ] + }, + "t" : [ 3582000.0, 3582000.0, 3582000.0, 3582000.0 ], + "ucharge_unc" : [ 999999995904.0, 999999995904.0, 999999995904.0, 999999995904.0 ], + "ucharge_val" : [ 0.0, 0.0, 0.0, 0.0 ], + "uwire_index" : [ 946, 946, 946, 946 ], + "vcharge_unc" : [ 1299.0, 1299.0, 1367.0, 1299.0 ], + "vcharge_val" : [ 5690.0, 8921.0, 10325.0, 5361.0 ], + "vwire_index" : [ 422, 423, 424, 425 ], + "wcharge_unc" : [ 195.0, 196.0, 195.0, 195.0 ], + "wcharge_val" : [ 861.0, 9300.0, 15557.0, 5613.0 ], + "wpid" : [ 7, 7, 7, 7 ], + "wwire_index" : [ 697, 698, 699, 700 ], + "x" : [ + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998 + ], + "x_t0cor" : [ + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998 + ], + "y" : [ + -897.89553541754981, + -896.16348286904304, + -894.43143032053626, + -892.69937777202949 + ], + "z" : [ + 2092.0096409733833, + 2095.0096316313552, + 2098.0096222893271, + 2101.009612947299 + ] + }, + { + "2dp0_x" : [ 2182.1819999999998, 2182.1819999999998, 2182.1819999999998 ], + "2dp0_y" : [ 1838.6018338641363, 1847.6018116498667, 1850.6018042451101 ], + "2dp1_x" : [ 2182.1819999999998, 2182.1819999999998, 2182.1819999999998 ], + "2dp1_y" : [ 259.39888981197987, 259.39884582719003, 259.39883116559338 ], + "2dp2_x" : [ 2182.1819999999998, 2182.1819999999998, 2182.1819999999998 ], + "2dp2_y" : [ 2098.0096222893271, 2107.0095942632429, 2110.0095849212148 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2182.1819999999998 ], + "center_y" : [ -915.79341175211994 ], + "center_z" : [ 2105.0096004912616 ], + "charge" : [ 90.637176513671875 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 3 ], + "slice_index_max" : [ 7168 ], + "slice_index_min" : [ 7164 ], + "u_wire_index_max" : [ 956 ], + "u_wire_index_min" : [ 951 ], + "v_wire_index_max" : [ 420 ], + "v_wire_index_min" : [ 419 ], + "w_wire_index_max" : [ 703 ], + "w_wire_index_min" : [ 698 ], + "wpid" : [ 7 ] + }, + "t" : [ 3582000.0, 3582000.0, 3582000.0 ], + "ucharge_unc" : [ 999999995904.0, 999999995904.0, 999999995904.0 ], + "ucharge_val" : [ 0.0, 0.0, 0.0 ], + "uwire_index" : [ 951, 954, 955 ], + "vcharge_unc" : [ 918.0, 918.0, 918.0 ], + "vcharge_val" : [ 150.0, 150.0, 150.0 ], + "vwire_index" : [ 419, 419, 419 ], + "wcharge_unc" : [ 195.0, 195.0, 0.0 ], + "wcharge_val" : [ 15557.0, 633.0, 0.0 ], + "wpid" : [ 7, 7, 7 ], + "wwire_index" : [ 699, 702, 703 ], + "x" : [ 2182.1819999999998, 2182.1819999999998, 2182.1819999999998 ], + "x_t0cor" : [ 2182.1819999999998, 2182.1819999999998, 2182.1819999999998 ], + "y" : [ -911.75195580560421, -916.94811345112453, -918.6801659996313 ], + "z" : [ 2098.0096222893271, 2107.0095942632429, 2110.0095849212148 ] + }, + { + "2dp0_x" : [ + 2184.384, + 2184.384, + 2184.384, + 2184.384, + 2184.384, + 2184.384, + 2184.384, + 2184.384 + ], + "2dp0_y" : [ + 1820.6018196462892, + 1820.6018049846925, + 1817.6017977278525, + 1820.6017903230959, + 1811.6017978757689, + 1817.6017830662558, + 1820.6017756614992, + 1811.6017538909789 + ], + "2dp1_x" : [ + 2184.384, + 2184.384, + 2184.384, + 2184.384, + 2184.384, + 2184.384, + 2184.384, + 2184.384 + ], + "2dp1_y" : [ + 271.39894816253366, + 274.39894075777704, + 277.39894801461708, + 277.39893335302042, + 280.39896993305388, + 280.39894060986046, + 280.39892594826381, + 289.39894771878403 + ], + "2dp2_x" : [ + 2184.384, + 2184.384, + 2184.384, + 2184.384, + 2184.384, + 2184.384, + 2184.384, + 2184.384 + ], + "2dp2_y" : [ + 2092.0096409733833, + 2095.0096316313552, + 2095.0096316313552, + 2098.0096222893271, + 2092.0096409733833, + 2098.0096222893271, + 2101.009612947299, + 2101.009612947299 + ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2184.384 ], + "center_y" : [ -888.36924640076245 ], + "center_z" : [ 2096.5096269603414 ], + "charge" : [ 21503.27734375 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 1 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 0 ], + "npoints" : [ 8 ], + "slice_index_max" : [ 7172 ], + "slice_index_min" : [ 7168 ], + "u_wire_index_max" : [ 946 ], + "u_wire_index_min" : [ 942 ], + "v_wire_index_max" : [ 430 ], + "v_wire_index_min" : [ 423 ], + "w_wire_index_max" : [ 702 ], + "w_wire_index_min" : [ 697 ], + "wpid" : [ 7 ] + }, + "t" : [ + 3584000.0, + 3584000.0, + 3584000.0, + 3584000.0, + 3584000.0, + 3584000.0, + 3584000.0, + 3584000.0 + ], + "ucharge_unc" : [ 971.0, 971.0, 932.0, 971.0, 971.0, 932.0, 971.0, 971.0 ], + "ucharge_val" : [ 9813.0, 9813.0, 6720.0, 9813.0, 1629.0, 6720.0, 9813.0, 1629.0 ], + "uwire_index" : [ 945, 945, 944, 945, 942, 944, 945, 942 ], + "vcharge_unc" : [ 1299.0, 1367.0, 1299.0, 1299.0, 1367.0, 1367.0, 1367.0, 1367.0 ], + "vcharge_val" : [ 3825.0, 9339.0, 10095.0, 10095.0, 5817.0, 5817.0, 5817.0, 444.0 ], + "vwire_index" : [ 423, 424, 425, 425, 426, 426, 426, 429 ], + "wcharge_unc" : [ 195.0, 196.0, 196.0, 195.0, 195.0, 195.0, 195.0, 195.0 ], + "wcharge_val" : [ 2449.0, 13002.0, 13002.0, 12732.0, 2449.0, 12732.0, 2705.0, 2705.0 ], + "wpid" : [ 7, 7, 7, 7, 7, 7, 7, 7 ], + "wwire_index" : [ 697, 698, 698, 699, 697, 699, 700, 700 ], + "x" : [ + 2184.384, + 2184.384, + 2184.384, + 2184.384, + 2184.384, + 2184.384, + 2184.384, + 2184.384 + ], + "x_t0cor" : [ + 2184.384, + 2184.384, + 2184.384, + 2184.384, + 2184.384, + 2184.384, + 2184.384, + 2184.384 + ], + "y" : [ + -894.43143032053626, + -892.69937777202949, + -889.23527267501595, + -890.96732522352272, + -884.03911502949563, + -887.50322012650918, + -889.23527267501595, + -878.84295738397532 + ], + "z" : [ + 2092.0096409733833, + 2095.0096316313552, + 2095.0096316313552, + 2098.0096222893271, + 2092.0096409733833, + 2098.0096222893271, + 2101.009612947299, + 2101.009612947299 + ] + }, + { + "2dp0_x" : [ 2184.384 ], + "2dp0_y" : [ 1826.6018341599695 ], + "2dp1_x" : [ 2184.384 ], + "2dp1_y" : [ 265.39893364885324 ], + "2dp2_x" : [ 2184.384 ], + "2dp2_y" : [ 2092.0096409733833 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2184.384 ], + "center_y" : [ -901.35964051456358 ], + "center_z" : [ 2092.0096409733833 ], + "charge" : [ 1.0 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 1 ], + "slice_index_max" : [ 7172 ], + "slice_index_min" : [ 7168 ], + "u_wire_index_max" : [ 948 ], + "u_wire_index_min" : [ 947 ], + "v_wire_index_max" : [ 422 ], + "v_wire_index_min" : [ 421 ], + "w_wire_index_max" : [ 698 ], + "w_wire_index_min" : [ 697 ], + "wpid" : [ 7 ] + }, + "t" : [ 3584000.0 ], + "ucharge_unc" : [ 932.0 ], + "ucharge_val" : [ 7100.0 ], + "uwire_index" : [ 947 ], + "vcharge_unc" : [ 1492.0 ], + "vcharge_val" : [ 1013.0 ], + "vwire_index" : [ 421 ], + "wcharge_unc" : [ 195.0 ], + "wcharge_val" : [ 2449.0 ], + "wpid" : [ 7 ], + "wwire_index" : [ 697 ], + "x" : [ 2184.384 ], + "x_t0cor" : [ 2184.384 ], + "y" : [ -901.35964051456358 ], + "z" : [ 2092.0096409733833 ] + }, + { + "2dp0_x" : [ 2184.384, 2184.384 ], + "2dp0_y" : [ 1826.601804836776, 1826.601760851986 ], + "2dp1_x" : [ 2184.384, 2184.384 ], + "2dp1_y" : [ 271.39891883934024, 280.39889662507073 ], + "2dp2_x" : [ 2184.384, 2184.384 ], + "2dp2_y" : [ 2098.0096222893271, 2107.0095942632429 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2184.384 ], + "center_y" : [ -895.29745659478965 ], + "center_z" : [ 2102.5096082762848 ], + "charge" : [ 7361.396484375 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 1 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 0 ], + "npoints" : [ 2 ], + "slice_index_max" : [ 7172 ], + "slice_index_min" : [ 7168 ], + "u_wire_index_max" : [ 948 ], + "u_wire_index_min" : [ 947 ], + "v_wire_index_max" : [ 427 ], + "v_wire_index_min" : [ 423 ], + "w_wire_index_max" : [ 702 ], + "w_wire_index_min" : [ 698 ], + "wpid" : [ 7 ] + }, + "t" : [ 3584000.0, 3584000.0 ], + "ucharge_unc" : [ 932.0, 932.0 ], + "ucharge_val" : [ 7100.0, 7100.0 ], + "uwire_index" : [ 947, 947 ], + "vcharge_unc" : [ 1299.0, 1367.0 ], + "vcharge_val" : [ 3825.0, 5817.0 ], + "vwire_index" : [ 423, 426 ], + "wcharge_unc" : [ 195.0, 0.0 ], + "wcharge_val" : [ 12732.0, 0.0 ], + "wpid" : [ 7, 7 ], + "wwire_index" : [ 699, 702 ], + "x" : [ 2184.384, 2184.384 ], + "x_t0cor" : [ 2184.384, 2184.384 ], + "y" : [ -897.89553541754981, -892.69937777202949 ], + "z" : [ 2098.0096222893271, 2107.0095942632429 ] + }, + { + "2dp0_x" : [ 2184.384, 2184.384, 2184.384, 2184.384 ], + "2dp0_y" : [ + 1823.6018122415326, + 1823.6017975799359, + 1823.6017682567426, + 1823.601753595146 + ], + "2dp1_x" : [ 2184.384, 2184.384, 2184.384, 2184.384 ], + "2dp1_y" : [ + 271.39893350093701, + 274.39892609618039, + 280.39891128666738, + 283.39890388191077 + ], + "2dp2_x" : [ 2184.384, 2184.384, 2184.384, 2184.384 ], + "2dp2_y" : [ + 2095.0096316313552, + 2098.0096222893271, + 2104.009603605271, + 2107.0095942632429 + ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2184.384 ], + "center_y" : [ -892.69937777202949 ], + "center_z" : [ 2101.009612947299 ], + "charge" : [ 2066.334716796875 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 1 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 0 ], + "npoints" : [ 4 ], + "slice_index_max" : [ 7172 ], + "slice_index_min" : [ 7168 ], + "u_wire_index_max" : [ 947 ], + "u_wire_index_min" : [ 946 ], + "v_wire_index_max" : [ 428 ], + "v_wire_index_min" : [ 423 ], + "w_wire_index_max" : [ 702 ], + "w_wire_index_min" : [ 697 ], + "wpid" : [ 7 ] + }, + "t" : [ 3584000.0, 3584000.0, 3584000.0, 3584000.0 ], + "ucharge_unc" : [ 999999995904.0, 999999995904.0, 999999995904.0, 999999995904.0 ], + "ucharge_val" : [ 0.0, 0.0, 0.0, 0.0 ], + "uwire_index" : [ 946, 946, 946, 946 ], + "vcharge_unc" : [ 1299.0, 1367.0, 1367.0, 1299.0 ], + "vcharge_val" : [ 3825.0, 9339.0, 5817.0, 2160.0 ], + "vwire_index" : [ 423, 424, 426, 427 ], + "wcharge_unc" : [ 196.0, 195.0, 195.0, 0.0 ], + "wcharge_val" : [ 13002.0, 12732.0, 386.0, 0.0 ], + "wpid" : [ 7, 7, 7, 7 ], + "wwire_index" : [ 698, 699, 701, 702 ], + "x" : [ 2184.384, 2184.384, 2184.384, 2184.384 ], + "x_t0cor" : [ 2184.384, 2184.384, 2184.384, 2184.384 ], + "y" : [ + -896.16348286904304, + -894.43143032053626, + -890.96732522352272, + -889.23527267501595 + ], + "z" : [ + 2095.0096316313552, + 2098.0096222893271, + 2104.009603605271, + 2107.0095942632429 + ] + }, + { + "2dp0_x" : [ 2184.384, 2184.384 ], + "2dp0_y" : [ 1838.6018045409432, 1841.6017971361866 ], + "2dp1_x" : [ 2184.384, 2184.384 ], + "2dp1_y" : [ 265.39887500246687, 265.39886034087021 ], + "2dp2_x" : [ 2184.384, 2184.384 ], + "2dp2_y" : [ 2104.009603605271, 2107.0095942632429 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2184.384 ], + "center_y" : [ -909.15387698284405 ], + "center_z" : [ 2105.5095989342572 ], + "charge" : [ 361.37741088867188 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 2 ], + "slice_index_max" : [ 7172 ], + "slice_index_min" : [ 7168 ], + "u_wire_index_max" : [ 953 ], + "u_wire_index_min" : [ 951 ], + "v_wire_index_max" : [ 422 ], + "v_wire_index_min" : [ 421 ], + "w_wire_index_max" : [ 702 ], + "w_wire_index_min" : [ 700 ], + "wpid" : [ 7 ] + }, + "t" : [ 3584000.0, 3584000.0 ], + "ucharge_unc" : [ 999999995904.0, 999999995904.0 ], + "ucharge_val" : [ 0.0, 0.0 ], + "uwire_index" : [ 951, 952 ], + "vcharge_unc" : [ 1492.0, 1492.0 ], + "vcharge_val" : [ 1013.0, 1013.0 ], + "vwire_index" : [ 421, 421 ], + "wcharge_unc" : [ 195.0, 0.0 ], + "wcharge_val" : [ 386.0, 0.0 ], + "wpid" : [ 7, 7 ], + "wwire_index" : [ 701, 702 ], + "x" : [ 2184.384, 2184.384 ], + "x_t0cor" : [ 2184.384, 2184.384 ], + "y" : [ -908.28785070859067, -910.01990325709744 ], + "z" : [ 2104.009603605271, 2107.0095942632429 ] + }, + { + "2dp0_x" : [ + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998 + ], + "2dp0_y" : [ + 1817.6018123894492, + 1820.6018049846925, + 1814.6018051326091, + 1817.6017977278525, + 1820.6017903230959, + 1811.6017978757689, + 1814.6017904710125, + 1817.6017830662558, + 1808.6017906189288, + 1811.6017832141722, + 1814.6017758094156, + 1817.6017684046592, + 1820.6017609999026, + 1808.6017612957355, + 1817.6017390814659 + ], + "2dp1_x" : [ + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998 + ], + "2dp1_y" : [ + 274.39895541937369, + 274.39894075777704, + 277.39896267621373, + 277.39894801461708, + 277.39893335302042, + 280.39896993305388, + 280.39895527145711, + 280.39894060986046, + 283.39897718989391, + 283.39896252829726, + 283.39894786670061, + 283.39893320510384, + 283.39891854350742, + 289.39896238038068, + 289.39891839559095 + ], + "2dp2_x" : [ + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998 + ], + "2dp2_y" : [ + 2092.0096409733833, + 2095.0096316313552, + 2092.0096409733833, + 2095.0096316313552, + 2098.0096222893271, + 2092.0096409733833, + 2095.0096316313552, + 2098.0096222893271, + 2092.0096409733833, + 2095.0096316313552, + 2098.0096222893271, + 2101.009612947299, + 2104.009603605271, + 2098.0096222893271, + 2107.0095942632429 + ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2186.5859999999998 ], + "center_y" : [ -885.88663774790302 ], + "center_z" : [ 2096.8096260261382 ], + "charge" : [ 31616.712890625 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 1 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 0 ], + "npoints" : [ 15 ], + "slice_index_max" : [ 7176 ], + "slice_index_min" : [ 7172 ], + "u_wire_index_max" : [ 946 ], + "u_wire_index_min" : [ 941 ], + "v_wire_index_max" : [ 430 ], + "v_wire_index_min" : [ 424 ], + "w_wire_index_max" : [ 702 ], + "w_wire_index_min" : [ 697 ], + "wpid" : [ 7 ] + }, + "t" : [ + 3586000.0, + 3586000.0, + 3586000.0, + 3586000.0, + 3586000.0, + 3586000.0, + 3586000.0, + 3586000.0, + 3586000.0, + 3586000.0, + 3586000.0, + 3586000.0, + 3586000.0, + 3586000.0, + 3586000.0 + ], + "ucharge_unc" : [ + 932.0, + 971.0, + 1046.0, + 932.0, + 971.0, + 971.0, + 1046.0, + 932.0, + 971.0, + 971.0, + 1046.0, + 932.0, + 971.0, + 971.0, + 932.0 + ], + "ucharge_val" : [ + 6467.0, + 6179.0, + 6605.0, + 6467.0, + 6179.0, + 6595.0, + 6605.0, + 6467.0, + 3879.0, + 6595.0, + 6605.0, + 6467.0, + 6179.0, + 3879.0, + 6467.0 + ], + "uwire_index" : [ + 944, + 945, + 943, + 944, + 945, + 942, + 943, + 944, + 941, + 942, + 943, + 944, + 945, + 941, + 944 + ], + "vcharge_unc" : [ + 1367.0, + 1367.0, + 1299.0, + 1299.0, + 1299.0, + 1367.0, + 1367.0, + 1367.0, + 1299.0, + 1299.0, + 1299.0, + 1299.0, + 1299.0, + 1367.0, + 1367.0 + ], + "vcharge_val" : [ + 3773.0, + 3773.0, + 8667.0, + 8667.0, + 8667.0, + 9921.0, + 9921.0, + 9921.0, + 7607.0, + 7607.0, + 7607.0, + 7607.0, + 7607.0, + 1151.0, + 1151.0 + ], + "vwire_index" : [ + 424, + 424, + 425, + 425, + 425, + 426, + 426, + 426, + 427, + 427, + 427, + 427, + 427, + 429, + 429 + ], + "wcharge_unc" : [ + 195.0, + 196.0, + 195.0, + 196.0, + 195.0, + 195.0, + 196.0, + 195.0, + 195.0, + 196.0, + 195.0, + 195.0, + 195.0, + 195.0, + 0.0 + ], + "wcharge_val" : [ + 5903.0, + 15972.0, + 5903.0, + 15972.0, + 8964.0, + 5903.0, + 15972.0, + 8964.0, + 5903.0, + 15972.0, + 8964.0, + 1238.0, + 439.0, + 8964.0, + 0.0 + ], + "wpid" : [ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 ], + "wwire_index" : [ + 697, + 698, + 697, + 698, + 699, + 697, + 698, + 699, + 697, + 698, + 699, + 700, + 701, + 699, + 702 + ], + "x" : [ + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998 + ], + "x_t0cor" : [ + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998 + ], + "y" : [ + -890.96732522352272, + -892.69937777202949, + -887.50322012650918, + -889.23527267501595, + -890.96732522352272, + -884.03911502949563, + -885.77116757800241, + -887.50322012650918, + -880.57500993248209, + -882.30706248098886, + -884.03911502949563, + -885.77116757800241, + -887.50322012650918, + -877.11090483546855, + -882.30706248098886 + ], + "z" : [ + 2092.0096409733833, + 2095.0096316313552, + 2092.0096409733833, + 2095.0096316313552, + 2098.0096222893271, + 2092.0096409733833, + 2095.0096316313552, + 2098.0096222893271, + 2092.0096409733833, + 2095.0096316313552, + 2098.0096222893271, + 2101.009612947299, + 2104.009603605271, + 2098.0096222893271, + 2107.0095942632429 + ] + }, + { + "2dp0_x" : [ 2186.5859999999998, 2186.5859999999998 ], + "2dp0_y" : [ 1838.6018045409432, 1841.6017971361866 ], + "2dp1_x" : [ 2186.5859999999998, 2186.5859999999998 ], + "2dp1_y" : [ 265.39887500246687, 265.39886034087021 ], + "2dp2_x" : [ 2186.5859999999998, 2186.5859999999998 ], + "2dp2_y" : [ 2104.009603605271, 2107.0095942632429 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2186.5859999999998 ], + "center_y" : [ -909.15387698284405 ], + "center_z" : [ 2105.5095989342572 ], + "charge" : [ 1.0 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 2 ], + "slice_index_max" : [ 7176 ], + "slice_index_min" : [ 7172 ], + "u_wire_index_max" : [ 953 ], + "u_wire_index_min" : [ 951 ], + "v_wire_index_max" : [ 422 ], + "v_wire_index_min" : [ 421 ], + "w_wire_index_max" : [ 702 ], + "w_wire_index_min" : [ 700 ], + "wpid" : [ 7 ] + }, + "t" : [ 3586000.0, 3586000.0 ], + "ucharge_unc" : [ 999999995904.0, 999999995904.0 ], + "ucharge_val" : [ 0.0, 0.0 ], + "uwire_index" : [ 951, 952 ], + "vcharge_unc" : [ 1492.0, 1492.0 ], + "vcharge_val" : [ 363.0, 363.0 ], + "vwire_index" : [ 421, 421 ], + "wcharge_unc" : [ 195.0, 0.0 ], + "wcharge_val" : [ 439.0, 0.0 ], + "wpid" : [ 7, 7 ], + "wwire_index" : [ 701, 702 ], + "x" : [ 2186.5859999999998, 2186.5859999999998 ], + "x_t0cor" : [ 2186.5859999999998, 2186.5859999999998 ], + "y" : [ -908.28785070859067, -910.01990325709744 ], + "z" : [ 2104.009603605271, 2107.0095942632429 ] + }, + { + "2dp0_x" : [ + 2188.788, + 2188.788, + 2188.788, + 2188.788, + 2188.788, + 2188.788, + 2188.788, + 2188.788, + 2188.788, + 2188.788, + 2188.788, + 2188.788 + ], + "2dp0_y" : [ + 1799.6017835100056, + 1808.601790618929, + 1808.6017759573324, + 1808.6017612957357, + 1811.6017978757691, + 1811.6017832141724, + 1811.6017685525758, + 1814.6017904710125, + 1814.6017758094158, + 1817.6017977278525, + 1817.6017537430628, + 1817.6017390814661 + ], + "2dp1_x" : [ + 2188.788, + 2188.788, + 2188.788, + 2188.788, + 2188.788, + 2188.788, + 2188.788, + 2188.788, + 2188.788, + 2188.788, + 2188.788, + 2188.788 + ], + "2dp1_y" : [ + 289.39900636517052, + 283.39897718989369, + 286.39896978513707, + 289.39896238038045, + 280.39896993305365, + 283.39896252829703, + 286.39895512354042, + 280.398955271457, + 283.39894786670038, + 277.39894801461685, + 286.39892580034734, + 289.39891839559073 + ], + "2dp2_x" : [ + 2188.788, + 2188.788, + 2188.788, + 2188.788, + 2188.788, + 2188.788, + 2188.788, + 2188.788, + 2188.788, + 2188.788, + 2188.788, + 2188.788 + ], + "2dp2_y" : [ + 2089.0096503154114, + 2092.0096409733833, + 2095.0096316313552, + 2098.0096222893271, + 2092.0096409733833, + 2095.0096316313552, + 2098.0096222893271, + 2095.0096316313552, + 2098.0096222893271, + 2095.0096316313552, + 2104.009603605271, + 2107.0095942632429 + ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2188.788 ], + "center_y" : [ -881.72971163148702 ], + "center_z" : [ 2096.5096269603409 ], + "charge" : [ 32603.970703125 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 12 ], + "slice_index_max" : [ 7180 ], + "slice_index_min" : [ 7176 ], + "u_wire_index_max" : [ 945 ], + "u_wire_index_min" : [ 938 ], + "v_wire_index_max" : [ 430 ], + "v_wire_index_min" : [ 425 ], + "w_wire_index_max" : [ 702 ], + "w_wire_index_min" : [ 696 ], + "wpid" : [ 7 ] + }, + "t" : [ + 3588000.0, + 3588000.0, + 3588000.0, + 3588000.0, + 3588000.0, + 3588000.0, + 3588000.0, + 3588000.0, + 3588000.0, + 3588000.0, + 3588000.0, + 3588000.0 + ], + "ucharge_unc" : [ + 898.0, + 971.0, + 971.0, + 971.0, + 971.0, + 971.0, + 971.0, + 1046.0, + 1046.0, + 932.0, + 932.0, + 932.0 + ], + "ucharge_val" : [ + 582.0, + 8216.0, + 8216.0, + 8216.0, + 9310.0, + 9310.0, + 9310.0, + 5893.0, + 5893.0, + 2370.0, + 2370.0, + 2370.0 + ], + "uwire_index" : [ 938, 941, 941, 941, 942, 942, 942, 943, 943, 944, 944, 944 ], + "vcharge_unc" : [ + 1367.0, + 1299.0, + 1552.0, + 1367.0, + 1367.0, + 1299.0, + 1552.0, + 1367.0, + 1299.0, + 1299.0, + 1552.0, + 1367.0 + ], + "vcharge_val" : [ + 3671.0, + 11429.0, + 7760.0, + 3671.0, + 6501.0, + 11429.0, + 7760.0, + 6501.0, + 11429.0, + 3532.0, + 7760.0, + 3671.0 + ], + "vwire_index" : [ 429, 427, 428, 429, 426, 427, 428, 426, 427, 425, 428, 429 ], + "wcharge_unc" : [ + 195.0, + 195.0, + 196.0, + 195.0, + 195.0, + 196.0, + 195.0, + 196.0, + 195.0, + 196.0, + 195.0, + 0.0 + ], + "wcharge_val" : [ + 1053.0, + 10309.0, + 15770.0, + 5193.0, + 10309.0, + 15770.0, + 5193.0, + 15770.0, + 5193.0, + 15770.0, + 284.0, + 0.0 + ], + "wpid" : [ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 ], + "wwire_index" : [ 696, 697, 698, 699, 697, 698, 699, 698, 699, 698, 701, 702 ], + "x" : [ + 2188.788, + 2188.788, + 2188.788, + 2188.788, + 2188.788, + 2188.788, + 2188.788, + 2188.788, + 2188.788, + 2188.788, + 2188.788, + 2188.788 + ], + "x_t0cor" : [ + 2188.788, + 2188.788, + 2188.788, + 2188.788, + 2188.788, + 2188.788, + 2188.788, + 2188.788, + 2188.788, + 2188.788, + 2188.788, + 2188.788 + ], + "y" : [ + -871.91474718994846, + -880.57500993248232, + -878.84295738397554, + -877.11090483546877, + -884.03911502949586, + -882.30706248098909, + -880.57500993248232, + -885.77116757800263, + -884.03911502949586, + -889.23527267501618, + -884.03911502949586, + -882.30706248098909 + ], + "z" : [ + 2089.0096503154114, + 2092.0096409733833, + 2095.0096316313552, + 2098.0096222893271, + 2092.0096409733833, + 2095.0096316313552, + 2098.0096222893271, + 2095.0096316313552, + 2098.0096222893271, + 2095.0096316313552, + 2104.009603605271, + 2107.0095942632429 + ] + }, + { + "2dp0_x" : [ 2188.788 ], + "2dp0_y" : [ 1829.6017681088265 ], + "2dp1_x" : [ 2188.788 ], + "2dp1_y" : [ 277.39888936823047 ], + "2dp2_x" : [ 2188.788 ], + "2dp2_y" : [ 2107.0095942632429 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2188.788 ], + "center_y" : [ -896.16348286904326 ], + "center_z" : [ 2107.0095942632429 ], + "charge" : [ 283.10125732421875 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 1 ], + "slice_index_max" : [ 7180 ], + "slice_index_min" : [ 7176 ], + "u_wire_index_max" : [ 949 ], + "u_wire_index_min" : [ 948 ], + "v_wire_index_max" : [ 426 ], + "v_wire_index_min" : [ 425 ], + "w_wire_index_max" : [ 702 ], + "w_wire_index_min" : [ 701 ], + "wpid" : [ 7 ] + }, + "t" : [ 3588000.0 ], + "ucharge_unc" : [ 787.0 ], + "ucharge_val" : [ 350.0 ], + "uwire_index" : [ 948 ], + "vcharge_unc" : [ 1299.0 ], + "vcharge_val" : [ 3532.0 ], + "vwire_index" : [ 425 ], + "wcharge_unc" : [ 0.0 ], + "wcharge_val" : [ 0.0 ], + "wpid" : [ 7 ], + "wwire_index" : [ 702 ], + "x" : [ 2188.788 ], + "x_t0cor" : [ 2188.788 ], + "y" : [ -896.16348286904326 ], + "z" : [ 2107.0095942632429 ] + }, + { + "2dp0_x" : [ + 2190.9899999999998, + 2190.9899999999998, + 2190.9899999999998, + 2190.9899999999998, + 2190.9899999999998, + 2190.9899999999998, + 2190.9899999999998, + 2190.9899999999998, + 2190.9899999999998 + ], + "2dp0_y" : [ + 1799.6017688484089, + 1802.601776105249, + 1805.601783362089, + 1805.6017687004924, + 1808.601790618929, + 1808.6017759573324, + 1808.6017466341391, + 1811.6017832141724, + 1814.6017758094158 + ], + "2dp1_x" : [ + 2190.9899999999998, + 2190.9899999999998, + 2190.9899999999998, + 2190.9899999999998, + 2190.9899999999998, + 2190.9899999999998, + 2190.9899999999998, + 2190.9899999999998, + 2190.9899999999998 + ], + "2dp1_y" : [ + 292.39899896041391, + 289.39899170357387, + 286.39898444673372, + 289.3989770419771, + 283.39897718989369, + 286.39896978513707, + 292.39895497562384, + 283.39896252829703, + 283.39894786670038 + ], + "2dp2_x" : [ + 2190.9899999999998, + 2190.9899999999998, + 2190.9899999999998, + 2190.9899999999998, + 2190.9899999999998, + 2190.9899999999998, + 2190.9899999999998, + 2190.9899999999998, + 2190.9899999999998 + ], + "2dp2_y" : [ + 2092.0096409733833, + 2092.0096409733833, + 2092.0096409733833, + 2095.0096316313552, + 2092.0096409733833, + 2095.0096316313552, + 2101.009612947299, + 2095.0096316313552, + 2098.0096222893271 + ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2190.9899999999998 ], + "center_y" : [ -877.49580540180352 ], + "center_z" : [ 2094.6762993360248 ], + "charge" : [ 28151.16796875 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 9 ], + "slice_index_max" : [ 7184 ], + "slice_index_min" : [ 7180 ], + "u_wire_index_max" : [ 944 ], + "u_wire_index_min" : [ 938 ], + "v_wire_index_max" : [ 431 ], + "v_wire_index_min" : [ 427 ], + "w_wire_index_max" : [ 701 ], + "w_wire_index_min" : [ 696 ], + "wpid" : [ 7 ] + }, + "t" : [ + 3590000.0, + 3590000.0, + 3590000.0, + 3590000.0, + 3590000.0, + 3590000.0, + 3590000.0, + 3590000.0, + 3590000.0 + ], + "ucharge_unc" : [ 898.0, 932.0, 971.0, 971.0, 971.0, 971.0, 971.0, 971.0, 1046.0 ], + "ucharge_val" : [ + 1768.0, + 5145.0, + 5996.0, + 5996.0, + 7411.0, + 7411.0, + 7411.0, + 6064.0, + 1468.0 + ], + "uwire_index" : [ 938, 939, 940, 940, 941, 941, 941, 942, 943 ], + "vcharge_unc" : [ + 1367.0, + 1367.0, + 1552.0, + 1367.0, + 1299.0, + 1552.0, + 1367.0, + 1299.0, + 1299.0 + ], + "vcharge_val" : [ + 3075.0, + 7958.0, + 8858.0, + 7958.0, + 7603.0, + 8858.0, + 3075.0, + 7603.0, + 7603.0 + ], + "vwire_index" : [ 430, 429, 428, 429, 427, 428, 430, 427, 427 ], + "wcharge_unc" : [ 195.0, 195.0, 195.0, 196.0, 195.0, 196.0, 195.0, 196.0, 195.0 ], + "wcharge_val" : [ + 12048.0, + 12048.0, + 12048.0, + 11919.0, + 12048.0, + 11919.0, + 358.0, + 11919.0, + 2588.0 + ], + "wpid" : [ 7, 7, 7, 7, 7, 7, 7, 7, 7 ], + "wwire_index" : [ 697, 697, 697, 698, 697, 698, 700, 698, 699 ], + "x" : [ + 2190.9899999999998, + 2190.9899999999998, + 2190.9899999999998, + 2190.9899999999998, + 2190.9899999999998, + 2190.9899999999998, + 2190.9899999999998, + 2190.9899999999998, + 2190.9899999999998 + ], + "x_t0cor" : [ + 2190.9899999999998, + 2190.9899999999998, + 2190.9899999999998, + 2190.9899999999998, + 2190.9899999999998, + 2190.9899999999998, + 2190.9899999999998, + 2190.9899999999998, + 2190.9899999999998 + ], + "y" : [ + -870.18269464144169, + -873.64679973845523, + -877.11090483546877, + -875.378852286962, + -880.57500993248232, + -878.84295738397554, + -875.378852286962, + -882.30706248098909, + -884.03911502949586 + ], + "z" : [ + 2092.0096409733833, + 2092.0096409733833, + 2092.0096409733833, + 2095.0096316313552, + 2092.0096409733833, + 2095.0096316313552, + 2101.009612947299, + 2095.0096316313552, + 2098.0096222893271 + ] + }, + { + "2dp0_x" : [ + 2193.192, + 2193.192, + 2193.192, + 2193.192, + 2193.192, + 2193.192, + 2193.192, + 2193.192, + 2193.192 + ], + "2dp0_y" : [ + 1799.6017688484089, + 1802.601776105249, + 1802.6017614436523, + 1805.601783362089, + 1805.6017687004924, + 1808.601790618929, + 1808.6017466341391, + 1811.6017832141724, + 1811.6017392293827 + ], + "2dp1_x" : [ + 2193.192, + 2193.192, + 2193.192, + 2193.192, + 2193.192, + 2193.192, + 2193.192, + 2193.192, + 2193.192 + ], + "2dp1_y" : [ + 292.39899896041391, + 289.39899170357387, + 292.39898429881725, + 286.39898444673372, + 289.3989770419771, + 283.39897718989369, + 292.39895497562384, + 283.39896252829703, + 292.39894031402741 + ], + "2dp2_x" : [ + 2193.192, + 2193.192, + 2193.192, + 2193.192, + 2193.192, + 2193.192, + 2193.192, + 2193.192, + 2193.192 + ], + "2dp2_y" : [ + 2092.0096409733833, + 2092.0096409733833, + 2095.0096316313552, + 2092.0096409733833, + 2095.0096316313552, + 2092.0096409733833, + 2101.009612947299, + 2095.0096316313552, + 2104.009603605271 + ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2193.1919999999996 ], + "center_y" : [ -875.95620313646418 ], + "center_z" : [ 2095.3429639266851 ], + "charge" : [ 19616.423828125 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 0 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 9 ], + "slice_index_max" : [ 7188 ], + "slice_index_min" : [ 7184 ], + "u_wire_index_max" : [ 943 ], + "u_wire_index_min" : [ 938 ], + "v_wire_index_max" : [ 431 ], + "v_wire_index_min" : [ 427 ], + "w_wire_index_max" : [ 701 ], + "w_wire_index_min" : [ 696 ], + "wpid" : [ 7 ] + }, + "t" : [ + 3592000.0, + 3592000.0, + 3592000.0, + 3592000.0, + 3592000.0, + 3592000.0, + 3592000.0, + 3592000.0, + 3592000.0 + ], + "ucharge_unc" : [ 898.0, 932.0, 932.0, 971.0, 971.0, 971.0, 971.0, 971.0, 971.0 ], + "ucharge_val" : [ + 3095.0, + 6189.0, + 6189.0, + 4804.0, + 4804.0, + 2585.0, + 2585.0, + 2081.0, + 2081.0 + ], + "uwire_index" : [ 938, 939, 939, 940, 940, 941, 941, 942, 942 ], + "vcharge_unc" : [ + 1367.0, + 1367.0, + 1367.0, + 1552.0, + 1367.0, + 1299.0, + 1367.0, + 1299.0, + 1367.0 + ], + "vcharge_val" : [ + 6200.0, + 7309.0, + 6200.0, + 4770.0, + 7309.0, + 1768.0, + 6200.0, + 1768.0, + 6200.0 + ], + "vwire_index" : [ 430, 429, 430, 428, 429, 427, 430, 427, 430 ], + "wcharge_unc" : [ 195.0, 195.0, 196.0, 195.0, 196.0, 195.0, 195.0, 196.0, 0.0 ], + "wcharge_val" : [ 9597.0, 9597.0, 6796.0, 9597.0, 6796.0, 9597.0, 451.0, 6796.0, 0.0 ], + "wpid" : [ 7, 7, 7, 7, 7, 7, 7, 7, 7 ], + "wwire_index" : [ 697, 697, 698, 697, 698, 697, 700, 698, 701 ], + "x" : [ + 2193.192, + 2193.192, + 2193.192, + 2193.192, + 2193.192, + 2193.192, + 2193.192, + 2193.192, + 2193.192 + ], + "x_t0cor" : [ + 2193.192, + 2193.192, + 2193.192, + 2193.192, + 2193.192, + 2193.192, + 2193.192, + 2193.192, + 2193.192 + ], + "y" : [ + -870.18269464144169, + -873.64679973845523, + -871.91474718994846, + -877.11090483546877, + -875.378852286962, + -880.57500993248232, + -875.378852286962, + -882.30706248098909, + -877.11090483546877 + ], + "z" : [ + 2092.0096409733833, + 2092.0096409733833, + 2095.0096316313552, + 2092.0096409733833, + 2095.0096316313552, + 2092.0096409733833, + 2101.009612947299, + 2095.0096316313552, + 2104.009603605271 + ] + }, + { + "2dp0_x" : [ 2195.3939999999998, 2195.3939999999998, 2195.3939999999998 ], + "2dp0_y" : [ 1804.0921414136633, 1798.0921268999823, 1807.0921327118151 ], + "2dp1_x" : [ 2195.3939999999998, 2195.3939999999998, 2195.3939999999998 ], + "2dp1_y" : [ 286.39899182484999, 292.39900633853085, 292.39896235360379 ], + "2dp2_x" : [ 2195.3939999999998, 2195.3939999999998, 2195.3939999999998 ], + "2dp2_y" : [ 2090.4999999999995, 2090.4999999999995, 2099.4999999999995 ], + "_array_types" : { + "2dp0_x" : "f8", + "2dp0_y" : "f8", + "2dp1_x" : "f8", + "2dp1_y" : "f8", + "2dp2_x" : "f8", + "2dp2_y" : "f8", + "t" : "f4", + "ucharge_unc" : "f8", + "ucharge_val" : "f8", + "uwire_index" : "i4", + "vcharge_unc" : "f8", + "vcharge_val" : "f8", + "vwire_index" : "i4", + "wcharge_unc" : "f8", + "wcharge_val" : "f8", + "wpid" : "i4", + "wwire_index" : "i4", + "x" : "f8", + "x_t0cor" : "f8", + "y" : "f8", + "z" : "f8" + }, + "_scalar" : { + "_array_types" : { + "center_x" : "f8", + "center_y" : "f8", + "center_z" : "f8", + "charge" : "f8", + "max_wire_interval" : "i4", + "max_wire_type" : "i4", + "min_wire_interval" : "i4", + "min_wire_type" : "i4", + "npoints" : "i4", + "slice_index_max" : "i4", + "slice_index_min" : "i4", + "u_wire_index_max" : "i4", + "u_wire_index_min" : "i4", + "v_wire_index_max" : "i4", + "v_wire_index_min" : "i4", + "w_wire_index_max" : "i4", + "w_wire_index_min" : "i4", + "wpid" : "i4" + }, + "center_x" : [ 2195.3939999999998 ], + "center_y" : [ -873.35256076881444 ], + "center_z" : [ 2093.4999999999995 ], + "charge" : [ 8105.52734375 ], + "max_wire_interval" : [ 3 ], + "max_wire_type" : [ 2 ], + "min_wire_interval" : [ 3 ], + "min_wire_type" : [ 1 ], + "npoints" : [ 3 ], + "slice_index_max" : [ 7192 ], + "slice_index_min" : [ 7188 ], + "u_wire_index_max" : [ 941 ], + "u_wire_index_min" : [ 938 ], + "v_wire_index_max" : [ 431 ], + "v_wire_index_min" : [ 428 ], + "w_wire_index_max" : [ 700 ], + "w_wire_index_min" : [ 696 ], + "wpid" : [ 7 ] + }, + "t" : [ 3594000.0, 3594000.0, 3594000.0 ], + "ucharge_unc" : [ 971.0, 898.0, 0.0 ], + "ucharge_val" : [ 1141.0, 2412.0, 0.0 ], + "uwire_index" : [ 940, 938, 941 ], + "vcharge_unc" : [ 1552.0, 1367.0, 1367.0 ], + "vcharge_val" : [ 1733.0, 4546.0, 4546.0 ], + "vwire_index" : [ 428, 430, 430 ], + "wcharge_unc" : [ 195.0, 195.0, 195.0 ], + "wcharge_val" : [ 1057.0, 1057.0, 514.0 ], + "wpid" : [ 7, 7, 7 ], + "wwire_index" : [ 696, 696, 699 ], + "x" : [ 2195.3939999999998, 2195.3939999999998, 2195.3939999999998 ], + "x_t0cor" : [ 2195.3939999999998, 2195.3939999999998, 2195.3939999999998 ], + "y" : [ -876.23930962268162, -869.31109942865362, -874.50727325510809 ], + "z" : [ 2090.4999999999995, 2090.4999999999995, 2099.4999999999995 ] + } + ], + "flags" : { + "main_cluster" : 1 + }, + "scope_coords" : [ "x_t0cor", "y", "z" ], + "scope_transform" : "T0Correction", + "steiner_graph" : { + "edges" : [ + [ 14, 4, 2.8584302036109817 ], + [ 27, 19, 5.4718218385885109 ], + [ 14, 15, 3.482522810049872 ], + [ 24, 26, 12.90634076373067 ], + [ 58, 59, 21.600033098849373 ], + [ 58, 60, 12.470778349248214 ], + [ 7, 8, 3.5445140119208429 ], + [ 38, 49, 6.7304732977464043 ], + [ 100, 101, 3.4357879452624673 ], + [ 100, 104, 6.3185277805749571 ], + [ 104, 102, 6.081670235894765 ], + [ 103, 104, 3.4150960026683537 ], + [ 103, 106, 9.1914825599602565 ], + [ 105, 97, 7.6201109558621996 ], + [ 104, 105, 3.4273552553650366 ], + [ 101, 99, 9.3247859597866984 ], + [ 100, 96, 5.6728393156281838 ], + [ 100, 97, 3.7092327282219153 ], + [ 100, 106, 2.2144036656004848 ], + [ 107, 108, 6.0522280496284315 ], + [ 113, 116, 3.9597047289747316 ], + [ 34, 52, 6.5502007685630295 ], + [ 49, 37, 2.3797925911330582 ], + [ 31, 40, 6.7064229161335938 ], + [ 29, 46, 6.320906042345384 ], + [ 46, 59, 5.2847999999999962 ], + [ 34, 47, 8.4984739864192544 ], + [ 35, 45, 8.7236699106153424 ], + [ 42, 41, 3.5812676568429582 ], + [ 42, 43, 3.6291268892204189 ], + [ 48, 36, 4.9256735112266492 ], + [ 64, 48, 5.2250323860495413 ], + [ 64, 54, 2.8641041974267316 ], + [ 6, 7, 3.4322614708999422 ], + [ 6, 8, 3.5574142350979696 ], + [ 6, 9, 9.1715520872583731 ], + [ 6, 10, 10.289353548067535 ], + [ 32, 33, 11.608546567727949 ], + [ 45, 47, 8.313852232832506 ], + [ 12, 13, 18.478777049196754 ], + [ 12, 14, 12.59614504268577 ], + [ 15, 29, 5.4894724985475092 ], + [ 0, 9, 2.885854264640257 ], + [ 1, 7, 2.7944707942652447 ], + [ 1, 12, 6.4233557041492508 ], + [ 20, 2, 5.1041072758446653 ], + [ 3, 2, 10.222366015883955 ], + [ 15, 6, 2.8065113716923857 ], + [ 8, 16, 3.9149078188373019 ], + [ 9, 23, 6.1826809255606845 ], + [ 10, 12, 2.8029729141322748 ], + [ 21, 25, 6.6290151473176477 ], + [ 31, 25, 2.2702829289880455 ], + [ 26, 13, 6.5673262813499678 ], + [ 26, 20, 4.2852270340062129 ], + [ 26, 22, 6.5414234207127508 ], + [ 26, 31, 4.1821615013819748 ], + [ 26, 44, 5.9632901656306005 ], + [ 29, 33, 2.4840752100103418 ], + [ 30, 35, 2.5182307187008193 ], + [ 136, 131, 4.0756258019000455 ], + [ 131, 133, 10.558728874294486 ], + [ 128, 132, 6.2305784905798545 ], + [ 134, 136, 3.4097240272721012 ], + [ 136, 135, 3.4323645090350503 ], + [ 136, 139, 3.3817903849834074 ], + [ 137, 140, 7.2635589200743151 ], + [ 132, 138, 4.0737468464121775 ], + [ 132, 140, 4.1319597602741922 ], + [ 132, 139, 4.0119724119935052 ], + [ 143, 142, 3.422593110188715 ], + [ 147, 145, 6.1382290826185599 ], + [ 146, 148, 12.309332875196572 ], + [ 147, 148, 6.0756027041745879 ], + [ 8, 9, 9.4685443968156999 ], + [ 8, 11, 12.873267924801754 ], + [ 109, 102, 5.6825136456185392 ], + [ 111, 113, 6.724946300156855 ], + [ 128, 133, 2.1949318138707334 ], + [ 112, 114, 10.106748456412165 ], + [ 113, 109, 3.9957125783866911 ], + [ 116, 115, 3.4135156902169141 ], + [ 116, 112, 2.1269769676240426 ], + [ 113, 107, 5.3454251797410519 ], + [ 22, 21, 3.5427233648326415 ], + [ 22, 23, 11.541375020055181 ], + [ 149, 150, 10.260918752940412 ], + [ 151, 150, 3.4123480572572387 ], + [ 151, 152, 3.4372581771734736 ], + [ 153, 148, 5.4439455147841596 ], + [ 153, 154, 6.2283681035750478 ], + [ 155, 149, 6.8467555991918321 ], + [ 155, 153, 6.4828167866903179 ], + [ 155, 157, 5.4259825790718414 ], + [ 153, 156, 6.9230623731344325 ], + [ 156, 157, 11.842515487857137 ], + [ 79, 80, 10.785638920395368 ], + [ 70, 75, 2.2038566566667006 ], + [ 87, 81, 2.2077892737791442 ], + [ 84, 82, 3.5158916771845865 ], + [ 82, 87, 4.1077445216475237 ], + [ 75, 85, 6.0887150792063842 ], + [ 84, 83, 3.5175975365912659 ], + [ 84, 79, 4.1425439371056312 ], + [ 11, 10, 3.442765662027385 ], + [ 64, 65, 6.1617854814230801 ], + [ 64, 67, 3.5844811056322845 ], + [ 42, 44, 9.7276301746949905 ], + [ 43, 45, 11.822520331746738 ], + [ 50, 51, 10.204687822754616 ], + [ 37, 40, 11.364831521131174 ], + [ 0, 1, 6.2130303442473673 ], + [ 143, 134, 2.7700511675410509 ], + [ 135, 141, 2.965810060202382 ], + [ 143, 137, 2.870789812382335 ], + [ 138, 145, 9.4016686501940825 ], + [ 141, 152, 9.1827898259647593 ], + [ 142, 152, 4.7057696670186395 ], + [ 143, 146, 2.2005849870791367 ], + [ 144, 130, 7.9548024442412428 ], + [ 147, 144, 2.1972909027233269 ], + [ 147, 143, 6.3029883757587468 ], + [ 147, 151, 2.7529992195245816 ], + [ 151, 154, 2.8867833890043681 ], + [ 50, 42, 4.2009291513381841 ], + [ 50, 57, 2.2965750874843391 ], + [ 50, 56, 6.5329904294564471 ], + [ 50, 54, 9.6918417022568057 ], + [ 50, 62, 5.0455157528554135 ], + [ 52, 60, 4.9256788869504557 ], + [ 56, 49, 4.2659173495748846 ], + [ 53, 61, 4.2295956900881553 ], + [ 61, 74, 10.885030808600851 ], + [ 69, 63, 2.8918119664573232 ], + [ 64, 71, 2.8281088364864417 ], + [ 66, 55, 5.2477047082456307 ], + [ 70, 66, 2.7980946456570188 ], + [ 67, 76, 6.7040990266233527 ], + [ 73, 68, 4.1775299278990294 ], + [ 70, 73, 6.3343810650637691 ], + [ 72, 70, 9.4101388510694068 ], + [ 79, 72, 2.2159889134789501 ], + [ 73, 65, 6.4708637208430515 ], + [ 77, 68, 10.800832813773555 ], + [ 87, 77, 4.5566523032396642 ], + [ 81, 79, 9.5349213949511924 ], + [ 85, 92, 11.775358703263274 ], + [ 86, 83, 6.4164158778541651 ], + [ 90, 86, 2.1777410827516204 ], + [ 95, 88, 4.2050563915502917 ], + [ 84, 92, 6.08245178561085 ], + [ 93, 99, 5.7908878246759992 ], + [ 98, 94, 2.8067200901689082 ], + [ 95, 98, 3.7334538627008791 ], + [ 108, 103, 2.7355852203625148 ], + [ 108, 110, 2.7280929909323857 ], + [ 110, 114, 4.0289671427497167 ], + [ 117, 111, 4.6730090332117564 ], + [ 115, 122, 6.313355438969805 ], + [ 116, 118, 2.7514296781409393 ], + [ 132, 127, 2.1618449381401663 ], + [ 136, 130, 4.1953458667890207 ], + [ 9, 11, 6.0266653460713631 ], + [ 54, 56, 3.545624334203846 ], + [ 56, 55, 6.1927581984731512 ], + [ 57, 58, 11.738398582786425 ], + [ 5, 3, 6.0989037750866926 ], + [ 4, 5, 3.5330343521429066 ], + [ 4, 8, 7.2346884595547074 ], + [ 87, 91, 2.1852069578055282 ], + [ 87, 90, 4.0484061132861902 ], + [ 88, 91, 9.3169770334669977 ], + [ 90, 89, 3.4386645075358282 ], + [ 89, 95, 4.1319480140856397 ], + [ 89, 94, 6.4403947323141857 ], + [ 90, 93, 4.1264281810428569 ], + [ 95, 96, 7.0929995838761419 ], + [ 98, 97, 3.4532506957864255 ], + [ 127, 123, 4.2232164221782131 ], + [ 117, 118, 3.456410939157943 ], + [ 117, 121, 6.3090466783613524 ], + [ 117, 126, 5.5279668542711571 ], + [ 128, 125, 4.0013972343342479 ], + [ 121, 119, 3.468341527445844 ], + [ 120, 129, 5.2647464559829062 ], + [ 121, 120, 3.4111438768453652 ], + [ 121, 122, 3.4043013226672301 ], + [ 123, 124, 11.401357799490453 ], + [ 119, 124, 5.4548981013251936 ], + [ 125, 126, 9.4119905895507934 ], + [ 128, 129, 3.3952073659279014 ], + [ 70, 71, 3.4669208106097922 ], + [ 78, 73, 7.2849167981809808 ], + [ 84, 78, 2.2288113237660445 ], + [ 73, 74, 10.055669191738675 ], + [ 73, 79, 4.1011429855675079 ], + [ 76, 80, 6.774807380133109 ], + [ 22, 34, 6.0491017412634092 ], + [ 36, 38, 14.020612604094881 ], + [ 42, 38, 3.5768920460704283 ], + [ 22, 14, 2.8334006196939976 ], + [ 26, 27, 3.5606040366677072 ], + [ 31, 41, 4.2268491140756304 ], + [ 31, 51, 6.1967650139445212 ], + [ 31, 32, 3.5436900667283453 ], + [ 62, 63, 19.419810776514698 ], + [ 15, 17, 3.4943900705732083 ], + [ 15, 18, 3.6021852574780269 ], + [ 56, 53, 6.2718179107258649 ], + [ 1, 5, 3.7648742647783293 ], + [ 26, 28, 3.5545072454142819 ], + [ 17, 28, 6.475810568613241 ], + [ 18, 30, 6.866656102132894 ], + [ 16, 19, 15.616698864206569 ], + [ 39, 24, 5.7881125941662015 ], + [ 50, 39, 2.2592937301728009 ], + [ 70, 69, 3.4585752230411462 ], + [ 3, 8, 12.000000000019394 ], + [ 3, 11, 15.099665663243579 ], + [ 4, 11, 15.874517956037998 ], + [ 5, 8, 6.0000000000096971 ], + [ 5, 11, 12.490002590857216 ], + [ 6, 11, 13.856420388054289 ], + [ 7, 11, 10.3923152910412 ], + [ 8, 10, 9.1651647115865096 ], + [ 12, 15, 10.392315291041882 ], + [ 13, 15, 10.3923155473036 ], + [ 15, 16, 6.9282103648690665 ], + [ 15, 19, 10.392315291041996 ], + [ 20, 22, 6.9281995775716503 ], + [ 25, 26, 3.4641050970139986 ], + [ 26, 29, 9.1651545185546013 ], + [ 26, 30, 12.489990978795875 ], + [ 31, 33, 12.490002590857106 ], + [ 31, 34, 11.999999704112103 ], + [ 31, 35, 13.856399155143301 ], + [ 36, 42, 13.856420388054175 ], + [ 37, 42, 9.165148499589538 ], + [ 39, 42, 3.4641050970139986 ], + [ 40, 42, 6.9282103648690665 ], + [ 42, 45, 12.489990978795875 ], + [ 42, 46, 17.320525485067265 ], + [ 42, 47, 15.874500580780721 ], + [ 48, 50, 12.490002590857106 ], + [ 49, 50, 6.9281995775716503 ], + [ 50, 52, 21.07131019060542 ], + [ 56, 57, 5.9999998520560514 ], + [ 56, 58, 15.874500580781316 ], + [ 56, 59, 26.153414244897959 ], + [ 56, 60, 23.999999408224205 ], + [ 61, 64, 10.392299366358158 ], + [ 62, 64, 12.49001306235413 ], + [ 63, 64, 6.9282103648690665 ], + [ 64, 66, 3.4641050970139986 ], + [ 68, 70, 9.1651565571810547 ], + [ 72, 73, 3.4641050970135439 ], + [ 73, 75, 5.9999998520848346 ], + [ 73, 76, 13.856377580714291 ], + [ 77, 79, 9.1651565571810547 ], + [ 78, 79, 3.4641050970135439 ], + [ 81, 84, 5.9999998520848346 ], + [ 84, 85, 11.999999704169669 ], + [ 86, 87, 3.4641050970135439 ], + [ 88, 90, 5.9999998520848346 ], + [ 90, 91, 3.4641050970135439 ], + [ 90, 92, 11.999999704169669 ], + [ 93, 95, 5.9999998520848346 ], + [ 94, 95, 3.4640943951785728 ], + [ 104, 106, 5.9999998520848346 ], + [ 112, 113, 3.4641050970135439 ], + [ 113, 114, 6.9282101940270877 ], + [ 127, 128, 5.9999998520848346 ], + [ 130, 132, 9.1651565571810547 ], + [ 131, 132, 5.9999998520848346 ], + [ 132, 133, 5.9999998520848346 ], + [ 136, 137, 3.4640943951785728 ], + [ 136, 138, 5.9999998520848346 ], + [ 136, 140, 5.9999998520848346 ], + [ 141, 143, 6.928199577572105 ], + [ 143, 144, 6.0000091941253499 ], + [ 146, 147, 6.0000091941253499 ], + [ 149, 151, 13.856420388054175 ] + ], + "num_vertices" : 158 + }, + "steiner_pc" : { + "flag_steiner_terminal" : [ + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 1, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 1, + 1, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1 + ], + "npoints" : 158, + "wpid" : [ + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7 + ], + "x_t0cor" : [ + 2118.3240000000001, + 2118.3240000000001, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2120.5259999999998, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2122.7280000000001, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2124.9299999999998, + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001, + 2127.1320000000001, + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998, + 2129.3339999999998, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2131.5360000000001, + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998, + 2133.7379999999998, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2135.9400000000001, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2138.1419999999998, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2140.3440000000001, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2142.5459999999998, + 2144.748, + 2144.748, + 2144.748, + 2144.748, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2146.9499999999998, + 2149.152, + 2149.152, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2151.3539999999998, + 2153.556, + 2153.556, + 2153.556, + 2153.556, + 2155.7579999999998, + 2155.7579999999998, + 2157.96, + 2157.96, + 2157.96, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2160.1619999999998, + 2162.364, + 2162.364, + 2164.5659999999998, + 2164.5659999999998, + 2166.768, + 2166.768, + 2166.768, + 2166.768, + 2168.9699999999998, + 2168.9699999999998, + 2171.172, + 2171.172, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2173.3739999999998, + 2175.576, + 2175.576, + 2175.576, + 2175.576, + 2177.7779999999998, + 2177.7779999999998, + 2177.7779999999998, + 2179.98, + 2179.98, + 2179.98, + 2179.98, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2182.1819999999998, + 2184.384, + 2184.384, + 2184.384, + 2184.384, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2186.5859999999998, + 2188.788, + 2188.788, + 2188.788, + 2188.788, + 2190.9899999999998, + 2190.9899999999998, + 2193.192, + 2195.3939999999998, + 2195.3939999999998 + ], + "y" : [ + -1027.7994765555577, + -1022.6033189100374, + -1010.47895107049, + -1020.0107550891472, + -1016.5466392048444, + -1020.0107443018584, + -1018.278686359707, + -1021.7427914567201, + -1020.0107335145686, + -1026.9389544958854, + -1028.6710016507473, + -1032.1351067477613, + -1026.9279514601681, + -1011.3394623426719, + -1014.8035782269748, + -1016.5356361691262, + -1019.999752053429, + -1013.0715310721122, + -1014.8035890142636, + -1006.1433208780842, + -1009.618439798107, + -1009.6184290108173, + -1013.0825341078313, + -1023.4748493988715, + -995.76200862276312, + -1004.4222767589416, + -1007.8863818559556, + -1006.1543239138042, + -1009.6184290108173, + -1016.5466392048444, + -1011.3504653783901, + -1004.4222767589416, + -1006.1543239138042, + -1016.5466392048444, + -1014.814581262693, + -1011.3504653783901, + -987.10175127387436, + -994.02997225519027, + -999.22612450706595, + -997.49406656491453, + -1004.4222875462314, + -1002.6902296040799, + -1000.9581716619285, + -999.22611371977712, + -1009.6184290108173, + -1004.4222551843631, + -1018.2786971469958, + -1011.3504653783901, + -985.36970411901177, + -994.02997225519027, + -997.49406656491453, + -1006.154334701093, + -1016.5466284175556, + -992.2979251003286, + -988.83380921602577, + -987.10175127387436, + -992.29791431303886, + -997.49406656491453, + -1002.6902080295014, + -1018.2786971469958, + -1013.0825233205416, + -992.2868681281617, + -999.21509989676736, + -983.62661077927112, + -987.09072666357395, + -981.89456362440853, + -983.62662156655995, + -985.35867950871136, + -974.105847551848, + -984.49816284288863, + -982.76611029438186, + -986.2302153913954, + -974.105847551848, + -977.56995264886154, + -984.49816284288863, + -982.76611029438186, + -984.49816284288863, + -965.44558480931414, + -970.64174245483446, + -974.105847551848, + -979.30200519736832, + -965.44558480931414, + -968.90968990632769, + -967.17763735782091, + -970.64174245483446, + -981.03405774587509, + -961.9814797123006, + -965.44558480931414, + -956.78532206678028, + -958.51737461528705, + -961.9814797123006, + -965.44558480931414, + -972.37379500334123, + -960.2494271637936, + -953.32121696976651, + -955.05326951827328, + -948.12505932424619, + -948.99661678130349, + -952.46072187831749, + -958.51737461528705, + -946.39300677573965, + -949.85711187275319, + -936.00069148469902, + -937.73274403320579, + -941.19684913021933, + -944.66095422723288, + -946.39300677573965, + -931.67610208352505, + -936.87225433540073, + -932.53658638768547, + -937.73274403320579, + -922.14427109664484, + -925.60837619365839, + -929.07248129067193, + -936.00069148469902, + -923.87632364515161, + -925.60837619365839, + -921.28378679248488, + -924.74789188949796, + -914.35558738574662, + -912.6235294435952, + -916.0876345406092, + -919.55173963762229, + -903.09169306307035, + -913.48400835411098, + -910.01990325709744, + -918.6801659996313, + -901.35964051456358, + -906.5557981600839, + -910.01990325709744, + -892.69937777202972, + -896.16348286904326, + -901.35964051456358, + -906.5557981600839, + -890.96732522352295, + -896.16348286904326, + -894.43143032053649, + -892.69937777202972, + -899.62758796605681, + -897.89553541755004, + -899.62758796605681, + -895.29189304990325, + -890.09574079802667, + -891.82779874017808, + -886.63163570101267, + -891.82778795288925, + -891.82779874017808, + -886.63163570101267, + -881.43547266184726, + -871.05427906672242, + -881.44659435776259, + -884.91069945477659, + -888.37480455178968, + -878.84295738397554, + -884.03911502949586, + -873.64679973845523, + -879.70341471969562, + -869.31109942865362 + ], + "z" : [ + 2125.0095382110744, + 2128.0095288690463, + 2119.0095568951306, + 2120.5000000000064, + 2126.5000000000064, + 2126.5000000000064, + 2129.5000000000064, + 2129.5000000000064, + 2132.5000000000064, + 2126.5000000000064, + 2129.5000000000064, + 2129.5000000000064, + 2129.4999999999995, + 2120.4999999999995, + 2126.4999999999995, + 2129.4999999999995, + 2135.4999999999995, + 2129.4999999999995, + 2132.4999999999995, + 2129.4999999999995, + 2120.5000000000064, + 2126.5000000000064, + 2126.5000000000064, + 2126.5000000000064, + 2126.5000000000064, + 2123.5000000000064, + 2123.5000000000064, + 2126.5000000000064, + 2126.5000000000064, + 2126.5000000000064, + 2135.5000000000064, + 2123.5000000000064, + 2126.5000000000064, + 2126.5000000000064, + 2129.5000000000064, + 2135.5000000000064, + 2123.5000000000064, + 2117.5000000000064, + 2120.5000000000064, + 2123.5000000000064, + 2117.5000000000064, + 2120.5000000000064, + 2123.5000000000064, + 2126.5000000000064, + 2126.5000000000064, + 2135.5000000000064, + 2123.5000000000064, + 2135.5000000000064, + 2120.5000000000064, + 2117.5000000000064, + 2123.5000000000064, + 2120.5000000000064, + 2132.5000000000064, + 2114.5000000000064, + 2120.5000000000064, + 2123.5000000000064, + 2120.5000000000064, + 2123.5000000000064, + 2132.5000000000064, + 2123.5000000000064, + 2132.5000000000064, + 2111.4999999999995, + 2123.4999999999995, + 2114.4999999999995, + 2120.4999999999995, + 2117.4999999999995, + 2120.4999999999995, + 2123.4999999999995, + 2116.0095662371587, + 2116.0095662371587, + 2119.0095568951306, + 2119.0095568951306, + 2116.0095662371587, + 2116.0095662371587, + 2110.0095849212148, + 2119.0095568951306, + 2128.0095288690463, + 2113.0095755791867, + 2116.0095662371587, + 2116.0095662371587, + 2125.0095382110744, + 2113.0095755791867, + 2113.0095755791867, + 2116.0095662371587, + 2116.0095662371587, + 2122.0095475531025, + 2113.0095755791867, + 2113.0095755791867, + 2110.0095849212148, + 2113.0095755791867, + 2113.0095755791867, + 2113.0095755791867, + 2119.0095568951306, + 2110.0095849212148, + 2110.0095849212148, + 2113.0095755791867, + 2113.0095755791867, + 2111.5000000000064, + 2111.5000000000064, + 2107.0095942632429, + 2110.0095849212148, + 2110.0095849212148, + 2104.009603605271, + 2107.0095942632429, + 2107.0095942632429, + 2107.0095942632429, + 2110.0095849212148, + 2105.5000000000064, + 2108.5000000000064, + 2104.009603605271, + 2107.0095942632429, + 2104.009603605271, + 2104.009603605271, + 2104.009603605271, + 2104.009603605271, + 2101.009612947299, + 2104.009603605271, + 2105.5000000000064, + 2105.5000000000064, + 2099.5000000000064, + 2102.5000000000064, + 2102.5000000000064, + 2102.5000000000064, + 2095.0096316313552, + 2095.0096316313552, + 2101.009612947299, + 2104.009603605271, + 2098.0096222893271, + 2101.009612947299, + 2101.009612947299, + 2095.0096316313552, + 2101.009612947299, + 2098.0096222893271, + 2101.009612947299, + 2098.0096222893271, + 2095.0096316313552, + 2098.0096222893271, + 2101.009612947299, + 2095.0096316313552, + 2098.0096222893271, + 2101.009612947299, + 2093.4999999999995, + 2096.4999999999995, + 2099.4999999999995, + 2096.4999999999995, + 2093.4999999999995, + 2099.4999999999995, + 2096.4999999999995, + 2093.4999999999995, + 2096.5000000000064, + 2096.5000000000064, + 2096.5000000000064, + 2096.5000000000064, + 2095.0096316313552, + 2098.0096222893271, + 2092.0096409733833, + 2090.4999999999995, + 2090.4999999999995 + ] + } + }, + "flag_back_search" : true, + "trackfitting_params" : { + "DL" : 6.4000000000000001e-07, + "DT" : 9.7999999999999993e-07, + "add_charge_uncer" : 600.0, + "add_sigma_L" : 1.5699936999999999, + "add_uncer_col" : 300.0, + "add_uncer_ind" : 0.0, + "area_ratio1" : 1.8, + "area_ratio2" : 1.7, + "charge_cut" : 2000.0, + "close_col_weight" : 0.45000000000000001, + "close_ind_weight" : 0.14999999999999999, + "col_sigma_w_T" : 0.11283600000000001, + "dead_col_weight" : 0.90000000000000002, + "dead_ind_weight" : 0.29999999999999999, + "default_charge_err" : 1000.0, + "default_charge_th" : 100.0, + "default_dQ_dx" : 5000.0, + "div_sigma" : 6.0, + "dx_norm_length" : 6.0, + "end_point_factor" : 0.59999999999999998, + "end_point_limit" : 6.0, + "ind_sigma_u_T" : 0.36269370000000001, + "ind_sigma_v_T" : 0.60448950000000001, + "lambda" : 0.00050000000000000001, + "low_dis_limit" : 12.0, + "mid_point_factor" : 0.90000000000000002, + "min_drift_time" : 50000.0, + "nlevel" : 3, + "overlap_th" : 0.5, + "rel_charge_uncer" : 0.10000000000000001, + "rel_uncer_col" : 0.050000000000000003, + "rel_uncer_ind" : 0.074999999999999997, + "scaling_quality_th" : 0.5, + "scaling_ratio" : 0.050000000000000003, + "search_range" : 10.0, + "share_charge_err" : 8000.0, + "skip_angle_cut_1" : 160.0, + "skip_angle_cut_2" : 90.0, + "skip_angle_cut_3" : 45.0, + "skip_default_ratio_1" : 0.25, + "skip_dis_cut" : 5.0, + "skip_ratio_1_cut" : 0.75, + "skip_ratio_cut" : 0.96999999999999997, + "time_tick_cut" : 20.0 + } +} diff --git a/clus/test/data/uboone-mabc_config.json b/clus/test/data/uboone-mabc_config.json new file mode 100644 index 000000000..f59478d89 --- /dev/null +++ b/clus/test/data/uboone-mabc_config.json @@ -0,0 +1,11954 @@ +[ + { + "data" : + { + "apps" : + [ + "Pgrapher" + ], + "plugins" : + [ + "WireCellSio", + "WireCellAux", + "WireCellGen", + "WireCellSigProc", + "WireCellImg", + "WireCellClus", + "WireCellRoot", + "WireCellApps", + "WireCellPgraph" + ] + }, + "type" : "wire-cell" + }, + { + "data" : + { + "filename" : "microboone-celltree-wires-v2.1.json.bz2" + }, + "type" : "WireSchemaFile" + }, + { + "data" : + { + "faces" : + [ + { + "anode" : 0, + "cathode" : 2548, + "response" : 94 + }, + null + ], + "ident" : 0, + "nimpacts" : 10, + "wire_schema" : "WireSchemaFile" + }, + "name" : "uboone", + "type" : "AnodePlane" + }, + { + "data" : + { + "anode" : "AnodePlane:uboone", + "input" : "uboone.root", + "kind" : "live", + "views" : "uvw" + }, + "name" : "live-uvw", + "type" : "UbooneBlobSource" + }, + { + "data" : + { + "anode" : "AnodePlane:uboone", + "input" : "uboone.root", + "kind" : "live", + "views" : "uv" + }, + "name" : "live-uv", + "type" : "UbooneBlobSource" + }, + { + "data" : + { + "anode" : "AnodePlane:uboone", + "input" : "uboone.root", + "kind" : "live", + "views" : "vw" + }, + "name" : "live-vw", + "type" : "UbooneBlobSource" + }, + { + "data" : + { + "anode" : "AnodePlane:uboone", + "input" : "uboone.root", + "kind" : "live", + "views" : "wu" + }, + "name" : "live-wu", + "type" : "UbooneBlobSource" + }, + { + "data" : + { + "multiplicity" : 4 + }, + "name" : "live", + "type" : "BlobSetMerge" + }, + { + "data" : + { + "drift_speed" : 0.001101, + "extra" : + [ + ".*wire_index", + ".*charge.*", + "wpid" + ], + "strategy" : + { + "disable_mix_dead_cell" : true, + "name" : "charge_stepped" + }, + "time_offset" : -1594550.4087193462 + }, + "name" : "live", + "type" : "BlobSampler" + }, + { + "data" : + { + "anode" : "AnodePlane:uboone", + "datapath" : "pointtrees/%d/live", + "flash" : "flash", + "flashlight" : "flashlight", + "input" : "uboone.root", + "kind" : "live", + "light" : "light", + "sampler" : "BlobSampler:live" + }, + "name" : "live", + "type" : "UbooneClusterSource" + }, + { + "data" : + { + "anodes" : + [ + "AnodePlane:uboone" + ], + "metadata" : + { + "a0f0pA" : + { + "FV_xmax" : 2550, + "FV_xmax_margin" : 20, + "FV_xmin" : 10, + "FV_xmin_margin" : 20, + "drift_speed" : 0.001101, + "nticks_live_slice" : 4, + "tick" : 500, + "tick_drift" : 0.55049999999999999, + "time_offset" : -1594550.4087193462 + }, + "overall" : + { + "FV_xmax" : 2550, + "FV_xmax_margin" : 20, + "FV_xmin" : 10, + "FV_xmin_margin" : 20, + "FV_ymax" : 1015, + "FV_ymax_margin" : 25, + "FV_ymin" : -995, + "FV_ymin_margin" : 25, + "FV_zmax" : 10220, + "FV_zmax_margin" : 30, + "FV_zmin" : 150, + "FV_zmin_margin" : 30, + "beam_dir" : + [ + 0, + 0, + 1 + ], + "vertical_dir" : + [ + 0, + 1, + 0 + ] + } + } + }, + "name" : "", + "type" : "DetectorVolumes" + }, + { + "data" : + { + "A" : 1, + "B" : 0.255, + "Efield" : 0.27300000000000002, + "Wi" : 2.3600000000000001e-05, + "rho" : 1.3799999999999999 + }, + "name" : "box_recomb", + "type" : "BoxRecombination" + }, + { + "data" : + { + "dedx_functions" : + { + "electron" : "LinterpFunction:ElectronDeDx", + "kaon" : "LinterpFunction:KaonDeDx", + "muon" : "LinterpFunction:MuonDeDx", + "pion" : "LinterpFunction:PionDeDx", + "proton" : "LinterpFunction:ProtonDeDx" + }, + "range_functions" : + { + "electron" : "LinterpFunction:ElectronRange", + "kaon" : "LinterpFunction:KaonRange", + "muon" : "LinterpFunction:MuonRange", + "pion" : "LinterpFunction:PionRange", + "proton" : "LinterpFunction:ProtonRange" + } + }, + "name" : "ParticleDataSet", + "type" : "ParticleDataSet" + }, + { + "data" : + { + "start" : 0.5, + "step" : 1, + "values" : + [ + 123417, + 92909.800000000003, + 82815.199999999997, + 76865.800000000003, + 72760.699999999997, + 69711.399999999994, + 67393, + 65380.900000000001, + 63780.900000000001, + 62367.800000000003, + 61186.099999999999, + 60132, + 59215.699999999997, + 58397.400000000001, + 57672.800000000003, + 57011.099999999999, + 56423.699999999997, + 55874.5, + 55387.199999999997, + 54922.099999999999, + 54513.400000000001, + 54118.199999999997, + 53772.300000000003, + 53441.599999999999, + 53138.5, + 52855.699999999997, + 52585.5, + 52340.699999999997, + 52100.599999999999, + 51880.900000000001, + 51667.300000000003, + 51472.599999999999, + 51296.300000000003, + 51123.699999999997, + 50965.199999999997, + 50810.699999999997, + 50698.199999999997, + 50618.300000000003, + 50538.5, + 50458.699999999997, + 50379.099999999999, + 50299.5, + 50220, + 50140.599999999999, + 50061.099999999999, + 49981.900000000001, + 49902.699999999997, + 49823.5, + 49744.400000000001, + 49665.400000000001, + 49586.5, + 49507.599999999999, + 49428.800000000003, + 49350.099999999999, + 49271.5, + 49193, + 49114.400000000001, + 49036, + 48957.699999999997, + 48879.400000000001 + ] + }, + "name" : "MuonDeDx", + "type" : "LinterpFunction" + }, + { + "data" : + { + "start" : 0.5, + "step" : 1, + "values" : + [ + 130057, + 98610.199999999997, + 88025.199999999997, + 81668.399999999994, + 77286, + 73976.399999999994, + 71361.800000000003, + 69240.5, + 67531.600000000006, + 65936.399999999994, + 64633.5, + 63463.5, + 62411.099999999999, + 61513.199999999997, + 60643.599999999999, + 59915.400000000001, + 59223, + 58582.800000000003, + 58029.699999999997, + 57485.099999999999, + 56999.400000000001, + 56554.699999999997, + 56116.5, + 55730.599999999999, + 55364.400000000001, + 55005.900000000001, + 54689.900000000001, + 54385.400000000001, + 54087.900000000001, + 53828, + 53576.5, + 53329.800000000003, + 53109.099999999999, + 52895.099999999999, + 52683.5, + 52493.099999999999, + 52308.800000000003, + 52125.900000000001, + 51956.800000000003, + 51794.699999999997, + 51633.300000000003, + 51486.900000000001, + 51353.699999999997, + 51220.699999999997, + 51092.900000000001, + 50973.800000000003, + 50855.300000000003, + 50747.599999999999, + 50680.900000000001, + 50620.400000000001, + 50560, + 50499.599999999999, + 50439.300000000003, + 50379, + 50318.699999999997, + 50258.5, + 50198.400000000001, + 50138.199999999997, + 50078.099999999999, + 50018.099999999999 + ] + }, + "name" : "PionDeDx", + "type" : "LinterpFunction" + }, + { + "data" : + { + "start" : 0.5, + "step" : 1, + "values" : + [ + 161542, + 126983, + 114622, + 106959, + 101705, + 97502.399999999994, + 93938.800000000003, + 91143.100000000006, + 88558.5, + 86538.399999999994, + 84582.800000000003, + 82933.399999999994, + 81465.600000000006, + 80031.899999999994, + 78835.199999999997, + 77710.399999999994, + 76608, + 75666.199999999997, + 74775.199999999997, + 73891.899999999994, + 73108.300000000003, + 72388.699999999997, + 71672.5, + 70991, + 70393.399999999994, + 69803.699999999997, + 69218, + 68708, + 68247.800000000003, + 67789.100000000006, + 67331.899999999994, + 66876.300000000003, + 66422.399999999994, + 65969.899999999994, + 65531.300000000003, + 65178.900000000001, + 64845.5, + 64513, + 64181.400000000001, + 63850.699999999997, + 63520.800000000003, + 63191.900000000001, + 62866.099999999999, + 62587.300000000003, + 62333.599999999999, + 62080.5, + 61827.900000000001, + 61575.800000000003, + 61324.300000000003, + 61073.300000000003, + 60822.900000000001, + 60578.5, + 60372.400000000001, + 60175.5, + 59978.800000000003, + 59782.5, + 59586.599999999999, + 59391, + 59195.699999999997, + 59000.800000000003 + ] + }, + "name" : "KaonDeDx", + "type" : "LinterpFunction" + }, + { + "data" : + { + "start" : -0.5, + "step" : 1, + "values" : + [ + 260000, + 100000, + 46999.400000000001, + 49203.800000000003, + 50881.300000000003, + 52163.400000000001, + 53252, + 54204.099999999999, + 55053.400000000001, + 55798.5, + 56464.599999999999, + 57058, + 57588, + 58061.599999999999, + 58485.599999999999, + 58863.099999999999, + 59051, + 59051, + 59051, + 59051, + 59051, + 59051, + 59051, + 59051, + 59051, + 59051, + 59051, + 59051, + 59051, + 59051, + 59051, + 59051, + 59051, + 59051, + 59051, + 59051, + 59051, + 59051, + 59051, + 59051, + 59051, + 59051, + 59051, + 59051, + 59051, + 59051, + 59051, + 59051, + 59051, + 59051, + 59051, + 59051, + 59051, + 59051, + 59051, + 59051, + 59051, + 59051, + 59051, + 59051 + ] + }, + "name" : "ElectronDeDx", + "type" : "LinterpFunction" + }, + { + "data" : + { + "start" : 0.5, + "step" : 1, + "values" : + [ + 178414, + 142899, + 129846, + 121659, + 115749, + 111151, + 107410, + 104269, + 101810, + 99594.899999999994, + 97398, + 95293.899999999994, + 93716.5, + 92245.600000000006, + 90784.199999999997, + 89338.5, + 88157.199999999997, + 87114.800000000003, + 86077.800000000003, + 85046, + 84036.300000000003, + 83210, + 82435.100000000006, + 81663.5, + 80895.199999999997, + 80130.600000000006, + 79464.800000000003, + 78869.899999999994, + 78277.100000000006, + 77686.300000000003, + 77097.600000000006, + 76518.5, + 76024, + 75553.600000000006, + 75084.5, + 74616.800000000003, + 74150.5, + 73688, + 73283.800000000003, + 72904.300000000003, + 72525.800000000003, + 72148.199999999997, + 71771.600000000006, + 71396, + 71038.600000000006, + 70724.800000000003, + 70413.5, + 70102.899999999994, + 69793, + 69483.800000000003, + 69175.199999999997, + 68894.300000000003, + 68651.399999999994, + 68409.199999999997, + 68167.300000000003, + 67925.899999999994, + 67685, + 67444.399999999994, + 67204.300000000003, + 66964.699999999997 + ] + }, + "name" : "ProtonDeDx", + "type" : "LinterpFunction" + }, + { + "data" : + { + "coords" : + [ + 2.75551e-06, + 0.0124163, + 0.041410299999999997, + 0.084544099999999997, + 0.140759, + 0.20916599999999999, + 0.289157, + 0.38015300000000002, + 0.48165599999999997, + 0.59320799999999996, + 0.71442700000000003, + 0.84494999999999998, + 0.98407699999999998, + 1.13107, + 1.28678, + 1.4508799999999999, + 1.6223399999999999, + 1.8018000000000001, + 1.98855, + 2.18215, + 2.3830399999999998, + 2.5903999999999998, + 2.8041499999999999, + 3.0245299999999999, + 3.25075, + 3.48291, + 3.72106, + 3.9645600000000001, + 4.2135999999999996, + 4.4680400000000002, + 4.72743, + 4.9919700000000002, + 5.2613599999999998, + 5.5353700000000003, + 5.81412, + 6.0970899999999997, + 6.3840500000000002, + 6.6751399999999999, + 6.9704699999999997, + 7.2701700000000002, + 7.5741699999999996, + 7.8816899999999999, + 8.1927199999999996, + 8.5073399999999992, + 8.8256399999999999, + 9.1477000000000004, + 9.4732099999999999, + 9.8017699999999994, + 10.1334, + 10.468299999999999, + 10.8063, + 11.1477, + 11.4918, + 11.8386, + 12.1881, + 12.5405, + 12.8956, + 13.253500000000001, + 13.613799999999999, + 13.9764, + 14.3414, + 14.7088, + 15.0787, + 15.450799999999999, + 15.824999999999999, + 16.2013, + 16.579699999999999, + 16.9603, + 17.343, + 17.727499999999999, + 18.113900000000001, + 18.502199999999998, + 18.892399999999999, + 19.284400000000002, + 19.6783, + 20.073799999999999, + 20.4709, + 20.869700000000002, + 21.270099999999999, + 21.6722, + 22.075800000000001, + 22.480799999999999, + 22.8873, + 23.295200000000001, + 23.704499999999999, + 24.115300000000001, + 24.5274, + 24.9407, + 25.3553, + 25.7712, + 26.188400000000001, + 26.6068, + 27.026299999999999, + 27.446899999999999, + 27.8688, + 28.291699999999999, + 28.715800000000002, + 29.140999999999998, + 29.5672, + 29.994499999999999, + 30.422699999999999, + 30.8521, + 31.282399999999999, + 31.713699999999999, + 32.145800000000001, + 32.578800000000001, + 33.012700000000002, + 33.447400000000002, + 33.883099999999999, + 34.319499999999998, + 34.756700000000002, + 35.194699999999997, + 35.633600000000001, + 36.0732, + 36.513300000000001, + 36.953800000000001, + 37.394799999999996, + 37.836100000000002, + 38.277900000000002, + 38.720100000000002, + 39.162799999999997, + 39.605800000000002, + 40.049300000000002, + 40.493200000000002, + 40.9375, + 41.382300000000001, + 41.827399999999997, + 42.273099999999999, + 42.719099999999997, + 43.165599999999998, + 43.612499999999997, + 44.059800000000003, + 44.507599999999996, + 44.9557, + 45.404400000000003, + 45.853400000000001, + 46.302900000000001, + 46.752899999999997, + 47.203200000000002, + 47.6541, + 48.1053, + 48.557000000000002, + 49.009099999999997, + 49.4617, + 49.914700000000003, + 50.368200000000002, + 50.822099999999999, + 51.276400000000002, + 51.731200000000001, + 52.186399999999999, + 52.642099999999999, + 53.098199999999999, + 53.5548, + 54.011899999999997, + 54.469299999999997, + 54.927300000000002, + 55.3857, + 55.844499999999996, + 56.303800000000003, + 56.763500000000001, + 57.223700000000001, + 57.684399999999997, + 58.145499999999998, + 58.607100000000003, + 59.069200000000002, + 59.531700000000001, + 59.994599999999998, + 60.458100000000002, + 60.921900000000001, + 61.386299999999999, + 61.850900000000003, + 62.315600000000003, + 62.780500000000004, + 63.245399999999997, + 63.710500000000003, + 64.175700000000006, + 64.641000000000005, + 65.106399999999994, + 65.571899999999999, + 66.037499999999994, + 66.503200000000007, + 66.969099999999997, + 67.435100000000006, + 67.9011, + 68.3673, + 68.833600000000004, + 69.3001, + 69.766599999999997, + 70.233199999999997, + 70.700000000000003, + 71.166899999999998, + 71.633899999999997, + 72.100999999999999, + 72.568200000000004, + 73.035499999999999, + 73.502899999999997, + 73.970500000000001, + 74.438199999999995, + 74.905900000000003, + 75.373800000000003, + 75.841800000000006, + 76.310000000000002, + 76.778199999999998, + 77.246600000000001, + 77.715000000000003, + 78.183599999999998, + 78.652299999999997, + 79.121099999999998, + 79.590000000000003, + 80.059100000000001, + 80.528199999999998, + 80.997500000000002, + 81.466899999999995, + 81.936400000000006, + 82.406000000000006, + 82.875699999999995, + 83.345600000000005, + 83.8155, + 84.285600000000002, + 84.755799999999994, + 85.226100000000002, + 85.6965, + 86.167000000000002, + 86.637699999999995, + 87.108400000000003, + 87.579300000000003, + 88.050299999999993, + 88.521199999999993, + 88.992199999999997, + 89.463099999999997, + 89.933999999999997, + 90.404899999999998, + 90.875799999999998, + 91.346699999999998, + 91.817499999999995, + 92.288399999999996, + 92.759200000000007, + 93.230099999999993, + 93.700900000000004, + 94.171700000000001, + 94.642499999999998, + 95.113299999999995, + 95.584100000000007, + 96.054900000000004, + 96.525599999999997, + 96.996399999999994, + 97.467100000000002, + 97.937899999999999, + 98.408600000000007, + 98.879300000000001, + 99.349999999999994, + 99.820700000000002, + 100.291, + 100.762, + 101.233, + 101.703, + 102.17400000000001, + 102.645, + 103.11499999999999, + 103.586, + 104.056, + 104.527, + 104.997, + 105.468, + 105.93899999999999, + 106.40900000000001, + 106.88, + 107.34999999999999, + 107.821, + 108.291, + 108.762, + 109.232, + 109.702, + 110.173, + 110.643, + 111.114, + 111.584, + 112.05500000000001, + 112.52500000000001, + 112.995, + 113.46599999999999, + 113.93600000000001, + 114.40600000000001, + 114.877, + 115.34699999999999, + 115.81699999999999, + 116.28700000000001, + 116.75700000000001, + 117.227, + 117.697, + 118.167, + 118.637, + 119.10599999999999, + 119.57599999999999, + 120.045, + 120.515, + 120.98399999999999, + 121.45399999999999, + 121.923, + 122.392, + 122.861, + 123.33, + 123.79900000000001, + 124.268, + 124.73699999999999, + 125.206, + 125.675, + 126.14400000000001, + 126.61199999999999, + 127.081, + 127.54900000000001, + 128.018, + 128.48599999999999, + 128.95500000000001, + 129.423, + 129.89099999999999, + 130.35900000000001, + 130.827, + 131.29499999999999, + 131.76300000000001, + 132.23099999999999, + 132.69900000000001, + 133.166, + 133.63399999999999, + 134.102, + 134.56899999999999, + 135.03700000000001, + 135.50399999999999, + 135.971, + 136.43899999999999, + 136.90600000000001, + 137.37299999999999, + 137.84, + 138.30699999999999, + 138.774, + 139.24100000000001, + 139.708, + 140.17400000000001, + 140.64099999999999, + 141.108, + 141.57400000000001, + 142.041, + 142.50700000000001, + 142.97300000000001, + 143.44, + 143.90600000000001, + 144.37200000000001, + 144.83799999999999, + 145.303, + 145.76900000000001, + 146.23500000000001, + 146.69999999999999, + 147.166, + 147.631, + 148.09700000000001, + 148.56200000000001, + 149.02699999999999, + 149.49199999999999, + 149.95699999999999, + 150.422, + 150.887, + 151.351, + 151.816, + 152.28100000000001, + 152.745, + 153.209, + 153.67400000000001, + 154.13800000000001, + 154.602, + 155.066, + 155.53, + 155.994, + 156.458, + 156.92099999999999, + 157.38499999999999, + 157.84800000000001, + 158.31200000000001, + 158.77500000000001, + 159.238, + 159.702, + 160.16499999999999, + 160.62799999999999, + 161.09100000000001, + 161.553, + 162.01599999999999, + 162.47900000000001, + 162.941, + 163.404, + 163.86600000000001, + 164.32900000000001, + 164.791, + 165.25299999999999, + 165.715, + 166.17699999999999, + 166.63900000000001, + 167.101, + 167.56299999999999, + 168.024, + 168.48599999999999, + 168.947, + 169.40899999999999, + 169.87, + 170.33099999999999, + 170.792, + 171.25399999999999, + 171.714, + 172.17500000000001, + 172.636, + 173.09700000000001, + 173.55799999999999, + 174.018, + 174.47900000000001, + 174.93899999999999, + 175.399, + 175.85900000000001, + 176.31999999999999, + 176.78, + 177.24000000000001, + 177.69999999999999, + 178.15899999999999, + 178.619, + 179.07900000000001, + 179.53800000000001, + 179.99799999999999, + 180.45699999999999, + 180.916, + 181.376, + 181.83500000000001, + 182.29400000000001, + 182.75299999999999, + 183.21199999999999, + 183.66999999999999, + 184.12899999999999, + 184.58799999999999, + 185.04599999999999, + 185.505, + 185.96299999999999, + 186.42099999999999, + 186.88, + 187.33799999999999, + 187.79599999999999, + 188.25399999999999, + 188.71199999999999, + 189.16900000000001, + 189.62700000000001, + 190.08500000000001, + 190.542, + 191, + 191.45699999999999, + 191.91399999999999, + 192.37200000000001, + 192.82900000000001, + 193.286, + 193.74299999999999, + 194.19999999999999, + 194.65600000000001, + 195.113, + 195.56999999999999, + 196.02600000000001, + 196.483, + 196.93899999999999, + 197.39500000000001, + 197.851, + 198.30699999999999, + 198.76300000000001, + 199.21899999999999, + 199.67500000000001, + 200.131, + 200.58699999999999, + 201.042, + 201.49799999999999, + 201.953, + 202.40799999999999, + 202.863, + 203.31899999999999, + 203.774, + 204.22900000000001, + 204.68299999999999, + 205.13800000000001, + 205.59299999999999, + 206.047, + 206.50200000000001, + 206.95599999999999, + 207.411, + 207.86500000000001, + 208.31899999999999, + 208.773, + 209.227, + 209.68100000000001, + 210.13499999999999, + 210.589, + 211.042, + 211.49600000000001, + 211.94900000000001, + 212.40299999999999, + 212.85599999999999, + 213.309, + 213.762, + 214.215, + 214.66800000000001, + 215.12100000000001, + 215.57400000000001, + 216.02600000000001, + 216.47900000000001, + 216.93199999999999, + 217.38399999999999, + 217.83600000000001, + 218.28899999999999, + 218.74100000000001, + 219.19300000000001, + 219.64500000000001, + 220.09700000000001, + 220.548, + 221, + 221.452, + 221.90299999999999, + 222.35499999999999, + 222.80600000000001, + 223.25800000000001, + 223.709, + 224.16, + 224.61099999999999, + 225.06200000000001, + 225.51300000000001, + 225.964, + 226.41399999999999, + 226.86500000000001, + 227.315, + 227.76599999999999, + 228.21600000000001, + 228.667, + 229.11699999999999, + 229.56700000000001, + 230.017, + 230.46700000000001, + 230.917, + 231.36600000000001, + 231.816, + 232.26599999999999, + 232.715, + 233.16499999999999, + 233.614, + 234.06299999999999, + 234.512, + 234.96100000000001, + 235.41, + 235.85900000000001, + 236.30799999999999, + 236.75700000000001, + 237.20599999999999, + 237.654, + 238.10300000000001, + 238.55099999999999, + 238.999, + 239.44800000000001, + 239.89599999999999, + 240.34399999999999, + 240.792, + 241.24000000000001, + 241.68799999999999, + 242.13499999999999, + 242.583, + 243.03, + 243.47800000000001, + 243.92500000000001, + 244.37299999999999, + 244.81999999999999, + 245.267, + 245.714, + 246.161, + 246.608, + 247.05500000000001, + 247.50200000000001, + 247.94800000000001, + 248.39500000000001, + 248.84100000000001, + 249.28800000000001, + 249.73400000000001, + 250.18000000000001, + 250.626, + 251.072, + 251.518, + 251.964, + 252.41, + 252.85599999999999, + 253.30199999999999, + 253.74700000000001, + 254.19300000000001, + 254.63800000000001, + 255.084, + 255.529, + 255.97399999999999, + 256.41899999999998, + 256.86399999999998, + 257.30900000000003, + 257.75400000000002, + 258.19900000000001, + 258.64299999999997, + 259.08800000000002, + 259.53300000000002, + 259.97699999999998, + 260.42200000000003, + 260.86599999999999, + 261.31, + 261.75400000000002, + 262.19799999999998, + 262.642, + 263.08600000000001, + 263.52999999999997, + 263.97399999999999, + 264.41699999999997, + 264.86099999999999, + 265.30399999999997, + 265.74799999999999, + 266.19099999999997, + 266.63400000000001, + 267.07799999999997, + 267.52100000000002, + 267.964, + 268.40699999999998, + 268.84899999999999, + 269.29199999999997, + 269.73500000000001, + 270.178, + 270.62, + 271.06200000000001, + 271.505, + 271.947, + 272.38900000000001, + 272.83100000000002, + 273.274, + 273.71600000000001, + 274.15699999999998, + 274.59899999999999, + 275.041, + 275.483, + 275.92399999999998, + 276.36599999999999, + 276.80700000000002, + 277.24799999999999, + 277.69, + 278.13099999999997, + 278.572, + 279.01299999999998, + 279.45400000000001, + 279.89499999999998, + 280.33600000000001, + 280.77600000000001, + 281.21699999999998, + 281.65699999999998, + 282.09800000000001, + 282.53800000000001, + 282.97800000000001, + 283.41899999999998, + 283.85899999999998, + 284.29899999999998, + 284.73899999999998, + 285.17899999999997, + 285.61900000000003, + 286.05799999999999, + 286.49799999999999, + 286.93799999999999, + 287.37700000000001, + 287.81700000000001, + 288.25599999999997, + 288.69499999999999, + 289.13400000000001, + 289.57299999999998, + 290.01299999999998, + 290.45100000000002, + 290.88999999999999, + 291.32900000000001, + 291.76799999999997, + 292.20699999999999, + 292.64499999999998, + 293.084, + 293.52199999999999, + 293.95999999999998, + 294.399, + 294.83699999999999, + 295.27499999999998, + 295.71300000000002, + 296.15100000000001, + 296.589, + 297.02699999999999, + 297.464, + 297.90199999999999, + 298.33999999999997, + 298.77699999999999, + 299.214, + 299.65199999999999, + 300.089, + 300.52600000000001, + 300.964, + 301.40100000000001, + 301.83800000000002, + 302.27499999999998, + 302.71100000000001, + 303.14800000000002, + 303.58499999999998, + 304.02199999999999, + 304.45800000000003, + 304.89499999999998, + 305.33100000000002, + 305.767, + 306.20400000000001, + 306.63999999999999, + 307.07600000000002, + 307.512, + 307.94799999999998, + 308.38400000000001, + 308.81999999999999, + 309.255, + 309.69099999999997, + 310.12700000000001, + 310.56200000000001, + 310.99799999999999, + 311.43299999999999, + 311.86900000000003, + 312.30399999999997, + 312.73899999999998, + 313.17399999999998, + 313.60899999999998, + 314.04399999999998, + 314.47899999999998, + 314.91399999999999, + 315.34800000000001, + 315.78300000000002, + 316.21800000000002, + 316.65199999999999, + 317.08699999999999, + 317.52100000000002, + 317.95499999999998, + 318.38999999999999, + 318.82400000000001, + 319.25799999999998, + 319.69200000000001, + 320.12599999999998, + 320.56, + 320.99400000000003, + 321.42700000000002, + 321.86099999999999, + 322.29399999999998, + 322.72800000000001, + 323.161, + 323.59500000000003, + 324.02800000000002, + 324.46100000000001, + 324.89400000000001, + 325.32799999999997, + 325.76100000000002, + 326.19299999999998, + 326.62599999999998, + 327.05900000000003, + 327.49200000000002, + 327.92399999999998, + 328.35700000000003, + 328.79000000000002, + 329.22199999999998, + 329.654, + 330.08699999999999, + 330.51900000000001, + 330.95100000000002, + 331.38299999999998, + 331.815, + 332.24700000000001, + 332.67899999999997, + 333.11099999999999, + 333.54199999999997, + 333.97399999999999, + 334.40600000000001, + 334.83699999999999, + 335.26799999999997, + 335.69999999999999, + 336.13099999999997, + 336.56200000000001, + 336.99299999999999, + 337.42500000000001, + 337.85500000000002, + 338.286, + 338.71699999999998, + 339.14800000000002, + 339.57900000000001, + 340.00900000000001, + 340.44, + 340.87, + 341.30099999999999, + 341.73099999999999, + 342.16199999999998, + 342.59199999999998, + 343.02199999999999, + 343.452, + 343.88200000000001, + 344.31200000000001, + 344.74200000000002, + 345.17200000000003, + 345.601, + 346.03100000000001, + 346.46100000000001, + 346.88999999999999, + 347.31999999999999, + 347.74900000000002, + 348.17899999999997, + 348.608, + 349.03699999999998, + 349.46600000000001, + 349.89600000000002, + 350.32499999999999, + 350.75400000000002, + 351.18299999999999, + 351.61099999999999, + 352.04000000000002, + 352.46899999999999, + 352.89800000000002, + 353.32600000000002, + 353.755, + 354.18299999999999, + 354.61200000000002, + 355.04000000000002, + 355.46800000000002, + 355.89699999999999, + 356.32499999999999, + 356.75299999999999, + 357.18099999999998, + 357.60899999999998, + 358.03699999999998, + 358.46499999999997, + 358.892, + 359.31999999999999, + 359.74799999999999, + 360.17500000000001, + 360.60300000000001, + 361.02999999999997, + 361.45800000000003, + 361.88499999999999, + 362.31200000000001, + 362.74000000000001, + 363.16699999999997, + 363.59399999999999, + 364.02100000000002, + 364.44799999999998, + 364.875, + 365.30200000000002, + 365.72800000000001, + 366.15499999999997, + 366.58199999999999, + 367.00799999999998, + 367.435, + 367.86099999999999, + 368.28800000000001, + 368.714, + 369.13999999999999, + 369.56700000000001, + 369.99299999999999, + 370.41899999999998, + 370.84500000000003, + 371.27100000000002, + 371.697, + 372.12200000000001, + 372.548, + 372.97399999999999, + 373.39999999999998, + 373.82499999999999, + 374.25099999999998, + 374.67599999999999, + 375.101, + 375.52699999999999, + 375.952, + 376.37700000000001, + 376.80200000000002, + 377.22699999999998, + 377.65199999999999, + 378.077, + 378.50200000000001, + 378.92700000000002, + 379.35199999999998, + 379.77699999999999, + 380.20100000000002, + 380.62599999999998, + 381.05000000000001, + 381.47500000000002, + 381.899, + 382.32299999999998, + 382.74799999999999, + 383.17200000000003, + 383.596, + 384.01999999999998, + 384.44400000000002, + 384.86799999999999, + 385.29199999999997, + 385.71600000000001, + 386.13999999999999, + 386.56299999999999, + 386.98700000000002, + 387.41000000000003, + 387.834, + 388.25700000000001, + 388.68099999999998, + 389.10399999999998, + 389.52699999999999, + 389.95100000000002, + 390.37400000000002, + 390.79700000000003, + 391.22000000000003, + 391.64299999999997, + 392.06599999999997, + 392.48899999999998, + 392.911, + 393.334, + 393.75700000000001, + 394.17899999999997, + 394.60199999999998, + 395.024, + 395.447, + 395.86900000000003, + 396.29199999999997, + 396.714, + 397.13600000000002, + 397.55799999999999, + 397.98000000000002, + 398.40199999999999, + 398.82400000000001, + 399.24599999999998, + 399.66800000000001, + 400.08999999999997, + 400.512, + 400.93299999999999, + 401.35500000000002, + 401.77699999999999, + 402.19799999999998, + 402.62, + 403.041, + 403.46199999999999, + 403.88400000000001, + 404.30500000000001, + 404.726, + 405.14699999999999, + 405.56799999999998, + 405.98899999999998, + 406.41000000000003, + 406.83100000000002, + 407.25200000000001, + 407.673, + 408.09399999999999, + 408.51400000000001, + 408.935, + 409.35500000000002, + 409.77600000000001, + 410.19600000000003, + 410.61700000000002, + 411.03699999999998, + 411.45699999999999, + 411.87700000000001, + 412.298, + 412.71800000000002, + 413.13799999999998, + 413.55799999999999, + 413.97800000000001, + 414.39800000000002, + 414.81700000000001, + 415.23700000000002, + 415.65699999999998, + 416.07600000000002, + 416.49599999999998, + 416.916, + 417.33499999999998, + 417.75400000000002, + 418.17399999999998, + 418.59300000000002, + 419.012, + 419.43200000000002, + 419.851, + 420.26999999999998, + 420.68900000000002, + 421.108, + 421.52699999999999, + 421.94600000000003, + 422.36399999999998, + 422.78300000000002, + 423.202, + 423.62, + 424.03899999999999, + 424.45699999999999, + 424.87599999999998, + 425.29399999999998, + 425.71300000000002, + 426.13099999999997, + 426.54899999999998, + 426.96699999999998, + 427.38499999999999, + 427.80399999999997, + 428.22199999999998, + 428.63900000000001, + 429.05700000000002, + 429.47500000000002, + 429.89299999999997, + 430.31099999999998, + 430.72800000000001, + 431.14600000000002, + 431.56400000000002, + 441.56400000000002, + 451.56400000000002, + 461.56400000000002, + 471.56400000000002, + 481.56400000000002, + 491.56400000000002, + 501.56400000000002, + 511.56400000000002, + 521.56399999999996, + 531.56399999999996, + 541.56399999999996, + 551.56399999999996, + 561.56399999999996, + 571.56399999999996, + 581.56399999999996, + 591.56399999999996, + 601.56399999999996, + 611.56399999999996, + 621.56399999999996, + 631.56399999999996, + 641.56399999999996, + 651.56399999999996, + 661.56399999999996, + 671.56399999999996, + 681.56399999999996, + 691.56399999999996, + 701.56399999999996, + 711.56399999999996, + 721.56399999999996, + 731.56399999999996, + 741.56399999999996, + 751.56399999999996, + 761.56399999999996, + 771.56399999999996, + 781.56399999999996, + 791.56399999999996, + 801.56399999999996, + 811.56399999999996, + 821.56399999999996, + 831.56399999999996, + 841.56399999999996, + 851.56399999999996, + 861.56399999999996, + 871.56399999999996, + 881.56399999999996, + 891.56399999999996, + 901.56399999999996, + 911.56399999999996, + 921.56399999999996, + 931.56399999999996, + 941.56399999999996, + 951.56399999999996, + 961.56399999999996, + 971.56399999999996, + 981.56399999999996, + 991.56399999999996, + 1001.5599999999999, + 1011.5599999999999, + 1021.5599999999999, + 1031.5599999999999, + 1041.5599999999999, + 1051.5599999999999, + 1061.5599999999999, + 1071.5599999999999, + 1081.5599999999999, + 1091.5599999999999, + 1101.5599999999999 + ], + "values" : + [ + 0.001, + 1.0009999999999999, + 2.0009999999999999, + 3.0009999999999999, + 4.0010000000000003, + 5.0010000000000003, + 6.0010000000000003, + 7.0010000000000003, + 8.0009999999999994, + 9.0009999999999994, + 10.000999999999999, + 11.000999999999999, + 12.000999999999999, + 13.000999999999999, + 14.000999999999999, + 15.000999999999999, + 16.001000000000001, + 17.001000000000001, + 18.001000000000001, + 19.001000000000001, + 20.001000000000001, + 21.001000000000001, + 22.001000000000001, + 23.001000000000001, + 24.001000000000001, + 25.001000000000001, + 26.001000000000001, + 27.001000000000001, + 28.001000000000001, + 29.001000000000001, + 30.001000000000001, + 31.001000000000001, + 32.000999999999998, + 33.000999999999998, + 34.000999999999998, + 35.000999999999998, + 36.000999999999998, + 37.000999999999998, + 38.000999999999998, + 39.000999999999998, + 40.000999999999998, + 41.000999999999998, + 42.000999999999998, + 43.000999999999998, + 44.000999999999998, + 45.000999999999998, + 46.000999999999998, + 47.000999999999998, + 48.000999999999998, + 49.000999999999998, + 50.000999999999998, + 51.000999999999998, + 52.000999999999998, + 53.000999999999998, + 54.000999999999998, + 55.000999999999998, + 56.000999999999998, + 57.000999999999998, + 58.000999999999998, + 59.000999999999998, + 60.000999999999998, + 61.000999999999998, + 62.000999999999998, + 63.000999999999998, + 64.001000000000005, + 65.001000000000005, + 66.001000000000005, + 67.001000000000005, + 68.001000000000005, + 69.001000000000005, + 70.001000000000005, + 71.001000000000005, + 72.001000000000005, + 73.001000000000005, + 74.001000000000005, + 75.001000000000005, + 76.001000000000005, + 77.001000000000005, + 78.001000000000005, + 79.001000000000005, + 80.001000000000005, + 81.001000000000005, + 82.001000000000005, + 83.001000000000005, + 84.001000000000005, + 85.001000000000005, + 86.001000000000005, + 87.001000000000005, + 88.001000000000005, + 89.001000000000005, + 90.001000000000005, + 91.001000000000005, + 92.001000000000005, + 93.001000000000005, + 94.001000000000005, + 95.001000000000005, + 96.001000000000005, + 97.001000000000005, + 98.001000000000005, + 99.001000000000005, + 100.001, + 101.001, + 102.001, + 103.001, + 104.001, + 105.001, + 106.001, + 107.001, + 108.001, + 109.001, + 110.001, + 111.001, + 112.001, + 113.001, + 114.001, + 115.001, + 116.001, + 117.001, + 118.001, + 119.001, + 120.001, + 121.001, + 122.001, + 123.001, + 124.001, + 125.001, + 126.001, + 127.001, + 128.001, + 129.001, + 130.001, + 131.001, + 132.001, + 133.001, + 134.001, + 135.001, + 136.001, + 137.001, + 138.001, + 139.001, + 140.001, + 141.001, + 142.001, + 143.001, + 144.001, + 145.001, + 146.001, + 147.001, + 148.001, + 149.001, + 150.001, + 151.001, + 152.001, + 153.001, + 154.001, + 155.001, + 156.001, + 157.001, + 158.001, + 159.001, + 160.001, + 161.001, + 162.001, + 163.001, + 164.001, + 165.001, + 166.001, + 167.001, + 168.001, + 169.001, + 170.001, + 171.001, + 172.001, + 173.001, + 174.001, + 175.001, + 176.001, + 177.001, + 178.001, + 179.001, + 180.001, + 181.001, + 182.001, + 183.001, + 184.001, + 185.001, + 186.001, + 187.001, + 188.001, + 189.001, + 190.001, + 191.001, + 192.001, + 193.001, + 194.001, + 195.001, + 196.001, + 197.001, + 198.001, + 199.001, + 200.001, + 201.001, + 202.001, + 203.001, + 204.001, + 205.001, + 206.001, + 207.001, + 208.001, + 209.001, + 210.001, + 211.001, + 212.001, + 213.001, + 214.001, + 215.001, + 216.001, + 217.001, + 218.001, + 219.001, + 220.001, + 221.001, + 222.001, + 223.001, + 224.001, + 225.001, + 226.001, + 227.001, + 228.001, + 229.001, + 230.001, + 231.001, + 232.001, + 233.001, + 234.001, + 235.001, + 236.001, + 237.001, + 238.001, + 239.001, + 240.001, + 241.001, + 242.001, + 243.001, + 244.001, + 245.001, + 246.001, + 247.001, + 248.001, + 249.001, + 250.001, + 251.001, + 252.001, + 253.001, + 254.001, + 255.001, + 256.00099999999998, + 257.00099999999998, + 258.00099999999998, + 259.00099999999998, + 260.00099999999998, + 261.00099999999998, + 262.00099999999998, + 263.00099999999998, + 264.00099999999998, + 265.00099999999998, + 266.00099999999998, + 267.00099999999998, + 268.00099999999998, + 269.00099999999998, + 270.00099999999998, + 271.00099999999998, + 272.00099999999998, + 273.00099999999998, + 274.00099999999998, + 275.00099999999998, + 276.00099999999998, + 277.00099999999998, + 278.00099999999998, + 279.00099999999998, + 280.00099999999998, + 281.00099999999998, + 282.00099999999998, + 283.00099999999998, + 284.00099999999998, + 285.00099999999998, + 286.00099999999998, + 287.00099999999998, + 288.00099999999998, + 289.00099999999998, + 290.00099999999998, + 291.00099999999998, + 292.00099999999998, + 293.00099999999998, + 294.00099999999998, + 295.00099999999998, + 296.00099999999998, + 297.00099999999998, + 298.00099999999998, + 299.00099999999998, + 300.00099999999998, + 301.00099999999998, + 302.00099999999998, + 303.00099999999998, + 304.00099999999998, + 305.00099999999998, + 306.00099999999998, + 307.00099999999998, + 308.00099999999998, + 309.00099999999998, + 310.00099999999998, + 311.00099999999998, + 312.00099999999998, + 313.00099999999998, + 314.00099999999998, + 315.00099999999998, + 316.00099999999998, + 317.00099999999998, + 318.00099999999998, + 319.00099999999998, + 320.00099999999998, + 321.00099999999998, + 322.00099999999998, + 323.00099999999998, + 324.00099999999998, + 325.00099999999998, + 326.00099999999998, + 327.00099999999998, + 328.00099999999998, + 329.00099999999998, + 330.00099999999998, + 331.00099999999998, + 332.00099999999998, + 333.00099999999998, + 334.00099999999998, + 335.00099999999998, + 336.00099999999998, + 337.00099999999998, + 338.00099999999998, + 339.00099999999998, + 340.00099999999998, + 341.00099999999998, + 342.00099999999998, + 343.00099999999998, + 344.00099999999998, + 345.00099999999998, + 346.00099999999998, + 347.00099999999998, + 348.00099999999998, + 349.00099999999998, + 350.00099999999998, + 351.00099999999998, + 352.00099999999998, + 353.00099999999998, + 354.00099999999998, + 355.00099999999998, + 356.00099999999998, + 357.00099999999998, + 358.00099999999998, + 359.00099999999998, + 360.00099999999998, + 361.00099999999998, + 362.00099999999998, + 363.00099999999998, + 364.00099999999998, + 365.00099999999998, + 366.00099999999998, + 367.00099999999998, + 368.00099999999998, + 369.00099999999998, + 370.00099999999998, + 371.00099999999998, + 372.00099999999998, + 373.00099999999998, + 374.00099999999998, + 375.00099999999998, + 376.00099999999998, + 377.00099999999998, + 378.00099999999998, + 379.00099999999998, + 380.00099999999998, + 381.00099999999998, + 382.00099999999998, + 383.00099999999998, + 384.00099999999998, + 385.00099999999998, + 386.00099999999998, + 387.00099999999998, + 388.00099999999998, + 389.00099999999998, + 390.00099999999998, + 391.00099999999998, + 392.00099999999998, + 393.00099999999998, + 394.00099999999998, + 395.00099999999998, + 396.00099999999998, + 397.00099999999998, + 398.00099999999998, + 399.00099999999998, + 400.00099999999998, + 401.00099999999998, + 402.00099999999998, + 403.00099999999998, + 404.00099999999998, + 405.00099999999998, + 406.00099999999998, + 407.00099999999998, + 408.00099999999998, + 409.00099999999998, + 410.00099999999998, + 411.00099999999998, + 412.00099999999998, + 413.00099999999998, + 414.00099999999998, + 415.00099999999998, + 416.00099999999998, + 417.00099999999998, + 418.00099999999998, + 419.00099999999998, + 420.00099999999998, + 421.00099999999998, + 422.00099999999998, + 423.00099999999998, + 424.00099999999998, + 425.00099999999998, + 426.00099999999998, + 427.00099999999998, + 428.00099999999998, + 429.00099999999998, + 430.00099999999998, + 431.00099999999998, + 432.00099999999998, + 433.00099999999998, + 434.00099999999998, + 435.00099999999998, + 436.00099999999998, + 437.00099999999998, + 438.00099999999998, + 439.00099999999998, + 440.00099999999998, + 441.00099999999998, + 442.00099999999998, + 443.00099999999998, + 444.00099999999998, + 445.00099999999998, + 446.00099999999998, + 447.00099999999998, + 448.00099999999998, + 449.00099999999998, + 450.00099999999998, + 451.00099999999998, + 452.00099999999998, + 453.00099999999998, + 454.00099999999998, + 455.00099999999998, + 456.00099999999998, + 457.00099999999998, + 458.00099999999998, + 459.00099999999998, + 460.00099999999998, + 461.00099999999998, + 462.00099999999998, + 463.00099999999998, + 464.00099999999998, + 465.00099999999998, + 466.00099999999998, + 467.00099999999998, + 468.00099999999998, + 469.00099999999998, + 470.00099999999998, + 471.00099999999998, + 472.00099999999998, + 473.00099999999998, + 474.00099999999998, + 475.00099999999998, + 476.00099999999998, + 477.00099999999998, + 478.00099999999998, + 479.00099999999998, + 480.00099999999998, + 481.00099999999998, + 482.00099999999998, + 483.00099999999998, + 484.00099999999998, + 485.00099999999998, + 486.00099999999998, + 487.00099999999998, + 488.00099999999998, + 489.00099999999998, + 490.00099999999998, + 491.00099999999998, + 492.00099999999998, + 493.00099999999998, + 494.00099999999998, + 495.00099999999998, + 496.00099999999998, + 497.00099999999998, + 498.00099999999998, + 499.00099999999998, + 500.00099999999998, + 501.00099999999998, + 502.00099999999998, + 503.00099999999998, + 504.00099999999998, + 505.00099999999998, + 506.00099999999998, + 507.00099999999998, + 508.00099999999998, + 509.00099999999998, + 510.00099999999998, + 511.00099999999998, + 512.00099999999998, + 513.00099999999998, + 514.00099999999998, + 515.00099999999998, + 516.00099999999998, + 517.00099999999998, + 518.00099999999998, + 519.00099999999998, + 520.00099999999998, + 521.00099999999998, + 522.00099999999998, + 523.00099999999998, + 524.00099999999998, + 525.00099999999998, + 526.00099999999998, + 527.00099999999998, + 528.00099999999998, + 529.00099999999998, + 530.00099999999998, + 531.00099999999998, + 532.00099999999998, + 533.00099999999998, + 534.00099999999998, + 535.00099999999998, + 536.00099999999998, + 537.00099999999998, + 538.00099999999998, + 539.00099999999998, + 540.00099999999998, + 541.00099999999998, + 542.00099999999998, + 543.00099999999998, + 544.00099999999998, + 545.00099999999998, + 546.00099999999998, + 547.00099999999998, + 548.00099999999998, + 549.00099999999998, + 550.00099999999998, + 551.00099999999998, + 552.00099999999998, + 553.00099999999998, + 554.00099999999998, + 555.00099999999998, + 556.00099999999998, + 557.00099999999998, + 558.00099999999998, + 559.00099999999998, + 560.00099999999998, + 561.00099999999998, + 562.00099999999998, + 563.00099999999998, + 564.00099999999998, + 565.00099999999998, + 566.00099999999998, + 567.00099999999998, + 568.00099999999998, + 569.00099999999998, + 570.00099999999998, + 571.00099999999998, + 572.00099999999998, + 573.00099999999998, + 574.00099999999998, + 575.00099999999998, + 576.00099999999998, + 577.00099999999998, + 578.00099999999998, + 579.00099999999998, + 580.00099999999998, + 581.00099999999998, + 582.00099999999998, + 583.00099999999998, + 584.00099999999998, + 585.00099999999998, + 586.00099999999998, + 587.00099999999998, + 588.00099999999998, + 589.00099999999998, + 590.00099999999998, + 591.00099999999998, + 592.00099999999998, + 593.00099999999998, + 594.00099999999998, + 595.00099999999998, + 596.00099999999998, + 597.00099999999998, + 598.00099999999998, + 599.00099999999998, + 600.00099999999998, + 601.00099999999998, + 602.00099999999998, + 603.00099999999998, + 604.00099999999998, + 605.00099999999998, + 606.00099999999998, + 607.00099999999998, + 608.00099999999998, + 609.00099999999998, + 610.00099999999998, + 611.00099999999998, + 612.00099999999998, + 613.00099999999998, + 614.00099999999998, + 615.00099999999998, + 616.00099999999998, + 617.00099999999998, + 618.00099999999998, + 619.00099999999998, + 620.00099999999998, + 621.00099999999998, + 622.00099999999998, + 623.00099999999998, + 624.00099999999998, + 625.00099999999998, + 626.00099999999998, + 627.00099999999998, + 628.00099999999998, + 629.00099999999998, + 630.00099999999998, + 631.00099999999998, + 632.00099999999998, + 633.00099999999998, + 634.00099999999998, + 635.00099999999998, + 636.00099999999998, + 637.00099999999998, + 638.00099999999998, + 639.00099999999998, + 640.00099999999998, + 641.00099999999998, + 642.00099999999998, + 643.00099999999998, + 644.00099999999998, + 645.00099999999998, + 646.00099999999998, + 647.00099999999998, + 648.00099999999998, + 649.00099999999998, + 650.00099999999998, + 651.00099999999998, + 652.00099999999998, + 653.00099999999998, + 654.00099999999998, + 655.00099999999998, + 656.00099999999998, + 657.00099999999998, + 658.00099999999998, + 659.00099999999998, + 660.00099999999998, + 661.00099999999998, + 662.00099999999998, + 663.00099999999998, + 664.00099999999998, + 665.00099999999998, + 666.00099999999998, + 667.00099999999998, + 668.00099999999998, + 669.00099999999998, + 670.00099999999998, + 671.00099999999998, + 672.00099999999998, + 673.00099999999998, + 674.00099999999998, + 675.00099999999998, + 676.00099999999998, + 677.00099999999998, + 678.00099999999998, + 679.00099999999998, + 680.00099999999998, + 681.00099999999998, + 682.00099999999998, + 683.00099999999998, + 684.00099999999998, + 685.00099999999998, + 686.00099999999998, + 687.00099999999998, + 688.00099999999998, + 689.00099999999998, + 690.00099999999998, + 691.00099999999998, + 692.00099999999998, + 693.00099999999998, + 694.00099999999998, + 695.00099999999998, + 696.00099999999998, + 697.00099999999998, + 698.00099999999998, + 699.00099999999998, + 700.00099999999998, + 701.00099999999998, + 702.00099999999998, + 703.00099999999998, + 704.00099999999998, + 705.00099999999998, + 706.00099999999998, + 707.00099999999998, + 708.00099999999998, + 709.00099999999998, + 710.00099999999998, + 711.00099999999998, + 712.00099999999998, + 713.00099999999998, + 714.00099999999998, + 715.00099999999998, + 716.00099999999998, + 717.00099999999998, + 718.00099999999998, + 719.00099999999998, + 720.00099999999998, + 721.00099999999998, + 722.00099999999998, + 723.00099999999998, + 724.00099999999998, + 725.00099999999998, + 726.00099999999998, + 727.00099999999998, + 728.00099999999998, + 729.00099999999998, + 730.00099999999998, + 731.00099999999998, + 732.00099999999998, + 733.00099999999998, + 734.00099999999998, + 735.00099999999998, + 736.00099999999998, + 737.00099999999998, + 738.00099999999998, + 739.00099999999998, + 740.00099999999998, + 741.00099999999998, + 742.00099999999998, + 743.00099999999998, + 744.00099999999998, + 745.00099999999998, + 746.00099999999998, + 747.00099999999998, + 748.00099999999998, + 749.00099999999998, + 750.00099999999998, + 751.00099999999998, + 752.00099999999998, + 753.00099999999998, + 754.00099999999998, + 755.00099999999998, + 756.00099999999998, + 757.00099999999998, + 758.00099999999998, + 759.00099999999998, + 760.00099999999998, + 761.00099999999998, + 762.00099999999998, + 763.00099999999998, + 764.00099999999998, + 765.00099999999998, + 766.00099999999998, + 767.00099999999998, + 768.00099999999998, + 769.00099999999998, + 770.00099999999998, + 771.00099999999998, + 772.00099999999998, + 773.00099999999998, + 774.00099999999998, + 775.00099999999998, + 776.00099999999998, + 777.00099999999998, + 778.00099999999998, + 779.00099999999998, + 780.00099999999998, + 781.00099999999998, + 782.00099999999998, + 783.00099999999998, + 784.00099999999998, + 785.00099999999998, + 786.00099999999998, + 787.00099999999998, + 788.00099999999998, + 789.00099999999998, + 790.00099999999998, + 791.00099999999998, + 792.00099999999998, + 793.00099999999998, + 794.00099999999998, + 795.00099999999998, + 796.00099999999998, + 797.00099999999998, + 798.00099999999998, + 799.00099999999998, + 800.00099999999998, + 801.00099999999998, + 802.00099999999998, + 803.00099999999998, + 804.00099999999998, + 805.00099999999998, + 806.00099999999998, + 807.00099999999998, + 808.00099999999998, + 809.00099999999998, + 810.00099999999998, + 811.00099999999998, + 812.00099999999998, + 813.00099999999998, + 814.00099999999998, + 815.00099999999998, + 816.00099999999998, + 817.00099999999998, + 818.00099999999998, + 819.00099999999998, + 820.00099999999998, + 821.00099999999998, + 822.00099999999998, + 823.00099999999998, + 824.00099999999998, + 825.00099999999998, + 826.00099999999998, + 827.00099999999998, + 828.00099999999998, + 829.00099999999998, + 830.00099999999998, + 831.00099999999998, + 832.00099999999998, + 833.00099999999998, + 834.00099999999998, + 835.00099999999998, + 836.00099999999998, + 837.00099999999998, + 838.00099999999998, + 839.00099999999998, + 840.00099999999998, + 841.00099999999998, + 842.00099999999998, + 843.00099999999998, + 844.00099999999998, + 845.00099999999998, + 846.00099999999998, + 847.00099999999998, + 848.00099999999998, + 849.00099999999998, + 850.00099999999998, + 851.00099999999998, + 852.00099999999998, + 853.00099999999998, + 854.00099999999998, + 855.00099999999998, + 856.00099999999998, + 857.00099999999998, + 858.00099999999998, + 859.00099999999998, + 860.00099999999998, + 861.00099999999998, + 862.00099999999998, + 863.00099999999998, + 864.00099999999998, + 865.00099999999998, + 866.00099999999998, + 867.00099999999998, + 868.00099999999998, + 869.00099999999998, + 870.00099999999998, + 871.00099999999998, + 872.00099999999998, + 873.00099999999998, + 874.00099999999998, + 875.00099999999998, + 876.00099999999998, + 877.00099999999998, + 878.00099999999998, + 879.00099999999998, + 880.00099999999998, + 881.00099999999998, + 882.00099999999998, + 883.00099999999998, + 884.00099999999998, + 885.00099999999998, + 886.00099999999998, + 887.00099999999998, + 888.00099999999998, + 889.00099999999998, + 890.00099999999998, + 891.00099999999998, + 892.00099999999998, + 893.00099999999998, + 894.00099999999998, + 895.00099999999998, + 896.00099999999998, + 897.00099999999998, + 898.00099999999998, + 899.00099999999998, + 900.00099999999998, + 901.00099999999998, + 902.00099999999998, + 903.00099999999998, + 904.00099999999998, + 905.00099999999998, + 906.00099999999998, + 907.00099999999998, + 908.00099999999998, + 909.00099999999998, + 910.00099999999998, + 911.00099999999998, + 912.00099999999998, + 913.00099999999998, + 914.00099999999998, + 915.00099999999998, + 916.00099999999998, + 917.00099999999998, + 918.00099999999998, + 919.00099999999998, + 920.00099999999998, + 921.00099999999998, + 922.00099999999998, + 923.00099999999998, + 924.00099999999998, + 925.00099999999998, + 926.00099999999998, + 927.00099999999998, + 928.00099999999998, + 929.00099999999998, + 930.00099999999998, + 931.00099999999998, + 932.00099999999998, + 933.00099999999998, + 934.00099999999998, + 935.00099999999998, + 936.00099999999998, + 937.00099999999998, + 938.00099999999998, + 939.00099999999998, + 940.00099999999998, + 941.00099999999998, + 942.00099999999998, + 943.00099999999998, + 944.00099999999998, + 945.00099999999998, + 946.00099999999998, + 947.00099999999998, + 948.00099999999998, + 949.00099999999998, + 950.00099999999998, + 951.00099999999998, + 952.00099999999998, + 953.00099999999998, + 954.00099999999998, + 955.00099999999998, + 956.00099999999998, + 957.00099999999998, + 958.00099999999998, + 959.00099999999998, + 960.00099999999998, + 961.00099999999998, + 962.00099999999998, + 963.00099999999998, + 964.00099999999998, + 965.00099999999998, + 966.00099999999998, + 967.00099999999998, + 968.00099999999998, + 969.00099999999998, + 970.00099999999998, + 971.00099999999998, + 972.00099999999998, + 973.00099999999998, + 974.00099999999998, + 975.00099999999998, + 976.00099999999998, + 977.00099999999998, + 978.00099999999998, + 979.00099999999998, + 980.00099999999998, + 981.00099999999998, + 982.00099999999998, + 983.00099999999998, + 984.00099999999998, + 985.00099999999998, + 986.00099999999998, + 987.00099999999998, + 988.00099999999998, + 989.00099999999998, + 990.00099999999998, + 991.00099999999998, + 992.00099999999998, + 993.00099999999998, + 994.00099999999998, + 995.00099999999998, + 996.00099999999998, + 997.00099999999998, + 998.00099999999998, + 999.00099999999998, + 1020, + 1041, + 1062, + 1083, + 1104, + 1125, + 1146, + 1167, + 1188, + 1209, + 1230, + 1251, + 1272, + 1293, + 1314, + 1335, + 1356, + 1377, + 1398, + 1419, + 1440, + 1461, + 1482, + 1503, + 1524, + 1545, + 1566, + 1587, + 1608, + 1629, + 1650, + 1671, + 1692, + 1713, + 1734, + 1755, + 1776, + 1797, + 1818, + 1839, + 1860, + 1881, + 1902, + 1923, + 1944, + 1965, + 1986, + 2007, + 2028, + 2049, + 2070, + 2091, + 2112, + 2133, + 2154, + 2175, + 2196, + 2217, + 2238, + 2259, + 2280, + 2301, + 2322, + 2343, + 2364, + 2385, + 2406 + ] + }, + "name" : "MuonRange", + "type" : "LinterpFunction" + }, + { + "data" : + { + "coords" : + [ + 3.15433e-06, + 0.010168999999999999, + 0.033600900000000003, + 0.068386799999999998, + 0.113515, + 0.16855600000000001, + 0.23294899999999999, + 0.30631900000000001, + 0.38829900000000001, + 0.47860200000000003, + 0.57691499999999996, + 0.68298000000000003, + 0.79656499999999997, + 0.91743300000000005, + 1.04538, + 1.18022, + 1.32115, + 1.4680500000000001, + 1.62144, + 1.78173, + 1.9477, + 2.1192799999999998, + 2.2968799999999998, + 2.4805000000000001, + 2.6691699999999998, + 2.8631000000000002, + 3.0625599999999999, + 3.26722, + 3.4765700000000002, + 3.6908099999999999, + 3.9101499999999998, + 4.13401, + 4.3622500000000004, + 4.5950300000000004, + 4.8324199999999999, + 5.0738599999999998, + 5.3193999999999999, + 5.5691800000000002, + 5.8230700000000004, + 6.0806899999999997, + 6.3421200000000004, + 6.6074900000000003, + 6.8765000000000001, + 7.1489799999999999, + 7.42502, + 7.70465, + 7.9873900000000004, + 8.2731499999999993, + 8.5619899999999998, + 8.85398, + 9.1491900000000008, + 9.4476899999999997, + 9.7495600000000007, + 10.054500000000001, + 10.362, + 10.6722, + 10.984999999999999, + 11.300599999999999, + 11.619, + 11.940300000000001, + 12.2643, + 12.5908, + 12.919499999999999, + 13.2506, + 13.584099999999999, + 13.9199, + 14.2583, + 14.5991, + 14.9422, + 15.2872, + 15.634399999999999, + 15.983499999999999, + 16.334800000000001, + 16.688199999999998, + 17.043800000000001, + 17.401399999999999, + 17.760899999999999, + 18.1221, + 18.485099999999999, + 18.849900000000002, + 19.2166, + 19.585100000000001, + 19.955400000000001, + 20.327500000000001, + 20.7011, + 21.0763, + 21.452999999999999, + 21.831399999999999, + 22.211400000000001, + 22.593, + 22.976199999999999, + 23.360800000000001, + 23.7468, + 24.1342, + 24.523, + 24.9132, + 25.3049, + 25.697900000000001, + 26.092300000000002, + 26.4879, + 26.884699999999999, + 27.282800000000002, + 27.682200000000002, + 28.082799999999999, + 28.4846, + 28.887699999999999, + 29.291799999999999, + 29.696999999999999, + 30.103300000000001, + 30.5107, + 30.9191, + 31.328700000000001, + 31.7394, + 32.151000000000003, + 32.563600000000001, + 32.9771, + 33.3917, + 33.807099999999998, + 34.223599999999998, + 34.640999999999998, + 35.059399999999997, + 35.4786, + 35.898699999999998, + 36.319600000000001, + 36.741399999999999, + 37.164099999999998, + 37.587600000000002, + 38.012, + 38.437199999999997, + 38.863199999999999, + 39.289999999999999, + 39.717500000000001, + 40.145899999999997, + 40.575000000000003, + 41.004899999999999, + 41.435600000000001, + 41.866900000000001, + 42.2988, + 42.731499999999997, + 43.1648, + 43.598799999999997, + 44.0334, + 44.468699999999998, + 44.904600000000002, + 45.341099999999997, + 45.778300000000002, + 46.216000000000001, + 46.654400000000003, + 47.093299999999999, + 47.532899999999998, + 47.972900000000003, + 48.413200000000003, + 48.8538, + 49.294699999999999, + 49.735900000000001, + 50.177500000000002, + 50.619399999999999, + 51.061599999999999, + 51.504100000000001, + 51.946899999999999, + 52.390099999999997, + 52.833599999999997, + 53.2774, + 53.721499999999999, + 54.165900000000001, + 54.610700000000001, + 55.055799999999998, + 55.501199999999997, + 55.947000000000003, + 56.393000000000001, + 56.839399999999998, + 57.286099999999998, + 57.733199999999997, + 58.180500000000002, + 58.6282, + 59.0762, + 59.5246, + 59.973300000000002, + 60.4223, + 60.871600000000001, + 61.321300000000001, + 61.771299999999997, + 62.221600000000002, + 62.6723, + 63.1233, + 63.574599999999997, + 64.026200000000003, + 64.478200000000001, + 64.930499999999995, + 65.383200000000002, + 65.836200000000005, + 66.289500000000004, + 66.743200000000002, + 67.197199999999995, + 67.651499999999999, + 68.106200000000001, + 68.561199999999999, + 69.016599999999997, + 69.472200000000001, + 69.928299999999993, + 70.384600000000006, + 70.841399999999993, + 71.298400000000001, + 71.755799999999994, + 72.213499999999996, + 72.671599999999998, + 73.129999999999995, + 73.588800000000006, + 74.047899999999998, + 74.507300000000001, + 74.967100000000002, + 75.427300000000002, + 75.887799999999999, + 76.348600000000005, + 76.809799999999996, + 77.271299999999997, + 77.733199999999997, + 78.195400000000006, + 78.658000000000001, + 79.120900000000006, + 79.584199999999996, + 80.047799999999995, + 80.511799999999994, + 80.976100000000002, + 81.440700000000007, + 81.905299999999997, + 82.370099999999994, + 82.834900000000005, + 83.299800000000005, + 83.764799999999994, + 84.229900000000001, + 84.695099999999996, + 85.160399999999996, + 85.625699999999995, + 86.091099999999997, + 86.556700000000006, + 87.022300000000001, + 87.488, + 87.953699999999998, + 88.419600000000003, + 88.885499999999993, + 89.351600000000005, + 89.817700000000002, + 90.283900000000003, + 90.750200000000007, + 91.216499999999996, + 91.683000000000007, + 92.149500000000003, + 92.616200000000006, + 93.082899999999995, + 93.549700000000001, + 94.016599999999997, + 94.483599999999996, + 94.950599999999994, + 95.4178, + 95.885000000000005, + 96.3523, + 96.819699999999997, + 97.287199999999999, + 97.754800000000003, + 98.222399999999993, + 98.690200000000004, + 99.158000000000001, + 99.625900000000001, + 100.09399999999999, + 100.562, + 101.03, + 101.498, + 101.967, + 102.435, + 102.904, + 103.372, + 103.84099999999999, + 104.31, + 104.779, + 105.248, + 105.717, + 106.18600000000001, + 106.655, + 107.124, + 107.59399999999999, + 108.063, + 108.533, + 109.003, + 109.47199999999999, + 109.94199999999999, + 110.41200000000001, + 110.88200000000001, + 111.352, + 111.822, + 112.29300000000001, + 112.76300000000001, + 113.233, + 113.70399999999999, + 114.17400000000001, + 114.645, + 115.116, + 115.587, + 116.05800000000001, + 116.529, + 117, + 117.471, + 117.94199999999999, + 118.41200000000001, + 118.883, + 119.354, + 119.825, + 120.29600000000001, + 120.767, + 121.238, + 121.709, + 122.18000000000001, + 122.65000000000001, + 123.121, + 123.592, + 124.063, + 124.53400000000001, + 125.004, + 125.47499999999999, + 125.946, + 126.417, + 126.88800000000001, + 127.358, + 127.82899999999999, + 128.30000000000001, + 128.77099999999999, + 129.24100000000001, + 129.71199999999999, + 130.18299999999999, + 130.65299999999999, + 131.124, + 131.595, + 132.066, + 132.536, + 133.00700000000001, + 133.477, + 133.94800000000001, + 134.41900000000001, + 134.88900000000001, + 135.36000000000001, + 135.83099999999999, + 136.30099999999999, + 136.77199999999999, + 137.24199999999999, + 137.71299999999999, + 138.184, + 138.654, + 139.125, + 139.595, + 140.066, + 140.536, + 141.00700000000001, + 141.477, + 141.94800000000001, + 142.41800000000001, + 142.88900000000001, + 143.35900000000001, + 143.83000000000001, + 144.30000000000001, + 144.77099999999999, + 145.24100000000001, + 145.71199999999999, + 146.18199999999999, + 146.65199999999999, + 147.12299999999999, + 147.59299999999999, + 148.06399999999999, + 148.53399999999999, + 149.00399999999999, + 149.47499999999999, + 149.94499999999999, + 150.41499999999999, + 150.886, + 151.35599999999999, + 151.82599999999999, + 152.297, + 152.767, + 153.23699999999999, + 153.70699999999999, + 154.17699999999999, + 154.64699999999999, + 155.11699999999999, + 155.58699999999999, + 156.05699999999999, + 156.52600000000001, + 156.99600000000001, + 157.46600000000001, + 157.935, + 158.405, + 158.874, + 159.34399999999999, + 159.81299999999999, + 160.28299999999999, + 160.75200000000001, + 161.221, + 161.69, + 162.16, + 162.62899999999999, + 163.09800000000001, + 163.56700000000001, + 164.036, + 164.505, + 164.97399999999999, + 165.44200000000001, + 165.911, + 166.38, + 166.84899999999999, + 167.31700000000001, + 167.786, + 168.25399999999999, + 168.72300000000001, + 169.191, + 169.66, + 170.12799999999999, + 170.596, + 171.06399999999999, + 171.53299999999999, + 172.001, + 172.46899999999999, + 172.93700000000001, + 173.405, + 173.87299999999999, + 174.34100000000001, + 174.80799999999999, + 175.27600000000001, + 175.744, + 176.21199999999999, + 176.679, + 177.14699999999999, + 177.614, + 178.08199999999999, + 178.54900000000001, + 179.017, + 179.48400000000001, + 179.95099999999999, + 180.41900000000001, + 180.886, + 181.35300000000001, + 181.81999999999999, + 182.28700000000001, + 182.75399999999999, + 183.221, + 183.68799999999999, + 184.155, + 184.62200000000001, + 185.08799999999999, + 185.55500000000001, + 186.02199999999999, + 186.488, + 186.95500000000001, + 187.42099999999999, + 187.88800000000001, + 188.35400000000001, + 188.81999999999999, + 189.286, + 189.75299999999999, + 190.21899999999999, + 190.685, + 191.15100000000001, + 191.61600000000001, + 192.08199999999999, + 192.548, + 193.01400000000001, + 193.47900000000001, + 193.94499999999999, + 194.41, + 194.876, + 195.34100000000001, + 195.80600000000001, + 196.27099999999999, + 196.73699999999999, + 197.202, + 197.667, + 198.13200000000001, + 198.59700000000001, + 199.06100000000001, + 199.52600000000001, + 199.99100000000001, + 200.45599999999999, + 200.91999999999999, + 201.38499999999999, + 201.84899999999999, + 202.31299999999999, + 202.77799999999999, + 203.24199999999999, + 203.70599999999999, + 204.16999999999999, + 204.63399999999999, + 205.09800000000001, + 205.56200000000001, + 206.02600000000001, + 206.49000000000001, + 206.95400000000001, + 207.417, + 207.881, + 208.34399999999999, + 208.80799999999999, + 209.27099999999999, + 209.73500000000001, + 210.19800000000001, + 210.661, + 211.124, + 211.58699999999999, + 212.05000000000001, + 212.51300000000001, + 212.976, + 213.43899999999999, + 213.90199999999999, + 214.364, + 214.827, + 215.28999999999999, + 215.75200000000001, + 216.215, + 216.67699999999999, + 217.13900000000001, + 217.601, + 218.06399999999999, + 218.52600000000001, + 218.988, + 219.44999999999999, + 219.91200000000001, + 220.374, + 220.83500000000001, + 221.297, + 221.75899999999999, + 222.22, + 222.68199999999999, + 223.143, + 223.60499999999999, + 224.066, + 224.52699999999999, + 224.989, + 225.44999999999999, + 225.911, + 226.37200000000001, + 226.833, + 227.29400000000001, + 227.755, + 228.215, + 228.67599999999999, + 229.137, + 229.59700000000001, + 230.05799999999999, + 230.518, + 230.97900000000001, + 231.43899999999999, + 231.899, + 232.36000000000001, + 232.81999999999999, + 233.28, + 233.74000000000001, + 234.19999999999999, + 234.66, + 235.119, + 235.57900000000001, + 236.03899999999999, + 236.49799999999999, + 236.958, + 237.41800000000001, + 237.87700000000001, + 238.33600000000001, + 238.79599999999999, + 239.255, + 239.714, + 240.173, + 240.63200000000001, + 241.09100000000001, + 241.55000000000001, + 242.00899999999999, + 242.46799999999999, + 242.92699999999999, + 243.38499999999999, + 243.84399999999999, + 244.30199999999999, + 244.761, + 245.21899999999999, + 245.678, + 246.136, + 246.59399999999999, + 247.05199999999999, + 247.511, + 247.96899999999999, + 248.42699999999999, + 248.88499999999999, + 249.34200000000001, + 249.80000000000001, + 250.25800000000001, + 250.71600000000001, + 251.173, + 251.631, + 252.08799999999999, + 252.54599999999999, + 253.00299999999999, + 253.46000000000001, + 253.91800000000001, + 254.375, + 254.83199999999999, + 255.28899999999999, + 255.74600000000001, + 256.20299999999997, + 256.66000000000003, + 257.11599999999999, + 257.57299999999998, + 258.02999999999997, + 258.48599999999999, + 258.94299999999998, + 259.399, + 259.85599999999999, + 260.31200000000001, + 260.76799999999997, + 261.22399999999999, + 261.68000000000001, + 262.13600000000002, + 262.59199999999998, + 263.048, + 263.50400000000002, + 263.95999999999998, + 264.416, + 264.87099999999998, + 265.327, + 265.78199999999998, + 266.238, + 266.69299999999998, + 267.14800000000002, + 267.60399999999998, + 268.05900000000003, + 268.51400000000001, + 268.96899999999999, + 269.42399999999998, + 269.87900000000002, + 270.334, + 270.78899999999999, + 271.24299999999999, + 271.69799999999998, + 272.15300000000002, + 272.60700000000003, + 273.06099999999998, + 273.51600000000002, + 273.97000000000003, + 274.42399999999998, + 274.87900000000002, + 275.33300000000003, + 275.78699999999998, + 276.24099999999999, + 276.69499999999999, + 277.149, + 277.60199999999998, + 278.05599999999998, + 278.50999999999999, + 278.96300000000002, + 279.41699999999997, + 279.87, + 280.32400000000001, + 280.77699999999999, + 281.23000000000002, + 281.68400000000003, + 282.137, + 282.58999999999997, + 283.04300000000001, + 283.49599999999998, + 283.94900000000001, + 284.40100000000001, + 284.85399999999998, + 285.30700000000002, + 285.75900000000001, + 286.21199999999999, + 286.66399999999999, + 287.11700000000002, + 287.56900000000002, + 288.02199999999999, + 288.47399999999999, + 288.92599999999999, + 289.37799999999999, + 289.82999999999998, + 290.28199999999998, + 290.73399999999998, + 291.18599999999998, + 291.63799999999998, + 292.089, + 292.541, + 292.99200000000002, + 293.44400000000002, + 293.89499999999998, + 294.34699999999998, + 294.798, + 295.24900000000002, + 295.69999999999999, + 296.15199999999999, + 296.60300000000001, + 297.05399999999997, + 297.505, + 297.95499999999998, + 298.40600000000001, + 298.85700000000003, + 299.30799999999999, + 299.75799999999998, + 300.209, + 300.65899999999999, + 301.11000000000001, + 301.56, + 302.00999999999999, + 302.45999999999998, + 302.911, + 303.36099999999999, + 303.81099999999998, + 304.26100000000002, + 304.70999999999998, + 305.16000000000003, + 305.61000000000001, + 306.06, + 306.50900000000001, + 306.959, + 307.40800000000002, + 307.858, + 308.30700000000002, + 308.75700000000001, + 309.20600000000002, + 309.65499999999997, + 310.10399999999998, + 310.553, + 311.00200000000001, + 311.45100000000002, + 311.89999999999998, + 312.34899999999999, + 312.79700000000003, + 313.24599999999998, + 313.69499999999999, + 314.14299999999997, + 314.59199999999998, + 315.04000000000002, + 315.488, + 315.93700000000001, + 316.38499999999999, + 316.83300000000003, + 317.28100000000001, + 317.72899999999998, + 318.17700000000002, + 318.625, + 319.07299999999998, + 319.52100000000002, + 319.96800000000002, + 320.416, + 320.863, + 321.31099999999998, + 321.75799999999998, + 322.20600000000002, + 322.65300000000002, + 323.10000000000002, + 323.548, + 323.995, + 324.44200000000001, + 324.88900000000001, + 325.33600000000001, + 325.78300000000002, + 326.22899999999998, + 326.67599999999999, + 327.12299999999999, + 327.56999999999999, + 328.01600000000002, + 328.46300000000002, + 328.90899999999999, + 329.35599999999999, + 329.80200000000002, + 330.24799999999999, + 330.69400000000002, + 331.13999999999999, + 331.58699999999999, + 332.03300000000002, + 332.47899999999998, + 332.92399999999998, + 333.37, + 333.81599999999997, + 334.262, + 334.70699999999999, + 335.15300000000002, + 335.59899999999999, + 336.04399999999998, + 336.48899999999998, + 336.935, + 337.38, + 337.82499999999999, + 338.26999999999998, + 338.71600000000001, + 339.161, + 339.60599999999999, + 340.05000000000001, + 340.495, + 340.94, + 341.38499999999999, + 341.82999999999998, + 342.274, + 342.71899999999999, + 343.16300000000001, + 343.608, + 344.05200000000002, + 344.49599999999998, + 344.94099999999997, + 345.38499999999999, + 345.82900000000001, + 346.27300000000002, + 346.71699999999998, + 347.161, + 347.60500000000002, + 348.048, + 348.49200000000002, + 348.93599999999998, + 349.38, + 349.82299999999998, + 350.267, + 350.70999999999998, + 351.154, + 351.59699999999998, + 352.04000000000002, + 352.483, + 352.92599999999999, + 353.37, + 353.81299999999999, + 354.25599999999997, + 354.69799999999998, + 355.14100000000002, + 355.584, + 356.02699999999999, + 356.46899999999999, + 356.91199999999998, + 357.35500000000002, + 357.79700000000003, + 358.23899999999998, + 358.68200000000002, + 359.12400000000002, + 359.56599999999997, + 360.00900000000001, + 360.45100000000002, + 360.89299999999997, + 361.33499999999998, + 361.77699999999999, + 362.21800000000002, + 362.66000000000003, + 363.10199999999998, + 363.54399999999998, + 363.98500000000001, + 364.42700000000002, + 364.86799999999999, + 365.31, + 365.75099999999998, + 366.19299999999998, + 366.63400000000001, + 367.07499999999999, + 367.51600000000002, + 367.95699999999999, + 368.39800000000002, + 368.839, + 369.27999999999997, + 369.721, + 370.16199999999998, + 370.60300000000001, + 371.04300000000001, + 371.48399999999998, + 371.92399999999998, + 372.36500000000001, + 372.80500000000001, + 373.24599999999998, + 373.68599999999998, + 374.12599999999998, + 374.56599999999997, + 375.00700000000001, + 375.447, + 375.887, + 376.327, + 376.76600000000002, + 377.20600000000002, + 377.64600000000002, + 378.08600000000001, + 378.52499999999998, + 378.96499999999997, + 379.40499999999997, + 379.84399999999999, + 380.28300000000002, + 380.72300000000001, + 381.16199999999998, + 381.601, + 382.04000000000002, + 382.48000000000002, + 382.91899999999998, + 383.358, + 383.79599999999999, + 384.23500000000001, + 384.67399999999998, + 385.113, + 385.55200000000002, + 385.99000000000001, + 386.42899999999997, + 386.86700000000002, + 387.30599999999998, + 387.74400000000003, + 388.18200000000002, + 388.62099999999998, + 389.05900000000003, + 389.49700000000001, + 389.935, + 390.37299999999999, + 390.81099999999998, + 391.24900000000002, + 391.68700000000001, + 392.125, + 392.56299999999999, + 393.00099999999998, + 393.43799999999999, + 393.87599999999998, + 394.31299999999999, + 394.75099999999998, + 395.18799999999999, + 395.62599999999998, + 396.06299999999999, + 396.5, + 396.93799999999999, + 397.375, + 397.81200000000001, + 398.24900000000002, + 398.68599999999998, + 399.12299999999999, + 399.56, + 399.99700000000001, + 400.43299999999999, + 400.87, + 401.30700000000002, + 401.74299999999999, + 402.18000000000001, + 402.61599999999999, + 403.053, + 403.48899999999998, + 403.92599999999999, + 404.36200000000002, + 404.798, + 405.23399999999998, + 405.67000000000002, + 406.10599999999999, + 406.54199999999997, + 406.97800000000001, + 407.41399999999999, + 407.85000000000002, + 408.286, + 408.72199999999998, + 409.15699999999998, + 409.59300000000002, + 410.02800000000002, + 410.464, + 410.899, + 411.33499999999998, + 411.76999999999998, + 412.20499999999998, + 412.64100000000002, + 413.07600000000002, + 413.51100000000002, + 413.94600000000003, + 414.38099999999997, + 414.81599999999997, + 415.25099999999998, + 415.68599999999998, + 416.12, + 416.55500000000001, + 416.99000000000001, + 417.42399999999998, + 417.85899999999998, + 418.29399999999998, + 418.72800000000001, + 419.16199999999998, + 419.59699999999998, + 420.03100000000001, + 420.46499999999997, + 420.899, + 421.334, + 421.76799999999997, + 422.202, + 422.63600000000002, + 423.06999999999999, + 423.50299999999999, + 423.93700000000001, + 424.37099999999998, + 424.80500000000001, + 425.238, + 425.67200000000003, + 426.10500000000002, + 426.53899999999999, + 426.97199999999998, + 427.40600000000001, + 427.839, + 428.27199999999999, + 428.70499999999998, + 429.13900000000001, + 429.572, + 430.005, + 430.43799999999999, + 430.87099999999998, + 431.303, + 431.73599999999999, + 432.16899999999998, + 432.60199999999998, + 433.03399999999999, + 433.46699999999998, + 433.89999999999998, + 443.89999999999998, + 453.89999999999998, + 463.89999999999998, + 473.89999999999998, + 483.89999999999998, + 493.89999999999998, + 503.89999999999998, + 513.89999999999998, + 523.89999999999998, + 533.89999999999998, + 543.89999999999998, + 553.89999999999998, + 563.89999999999998, + 573.89999999999998, + 583.89999999999998, + 593.89999999999998, + 603.89999999999998, + 613.89999999999998, + 623.89999999999998, + 633.89999999999998, + 643.89999999999998, + 653.89999999999998, + 663.89999999999998, + 673.89999999999998, + 683.89999999999998, + 693.89999999999998, + 703.89999999999998, + 713.89999999999998, + 723.89999999999998, + 733.89999999999998, + 743.89999999999998, + 753.89999999999998, + 763.89999999999998, + 773.89999999999998, + 783.89999999999998, + 793.89999999999998, + 803.89999999999998, + 813.89999999999998, + 823.89999999999998, + 833.89999999999998, + 843.89999999999998, + 853.89999999999998, + 863.89999999999998, + 873.89999999999998, + 883.89999999999998, + 893.89999999999998, + 903.89999999999998, + 913.89999999999998, + 923.89999999999998, + 933.89999999999998, + 943.89999999999998, + 953.89999999999998, + 963.89999999999998, + 973.89999999999998, + 983.89999999999998, + 993.89999999999998, + 1003.9, + 1013.9, + 1023.9, + 1033.9000000000001, + 1043.9000000000001, + 1053.9000000000001, + 1063.9000000000001, + 1073.9000000000001, + 1083.9000000000001, + 1093.9000000000001, + 1103.9000000000001 + ], + "values" : + [ + 0.001, + 1.0009999999999999, + 2.0009999999999999, + 3.0009999999999999, + 4.0010000000000003, + 5.0010000000000003, + 6.0010000000000003, + 7.0010000000000003, + 8.0009999999999994, + 9.0009999999999994, + 10.000999999999999, + 11.000999999999999, + 12.000999999999999, + 13.000999999999999, + 14.000999999999999, + 15.000999999999999, + 16.001000000000001, + 17.001000000000001, + 18.001000000000001, + 19.001000000000001, + 20.001000000000001, + 21.001000000000001, + 22.001000000000001, + 23.001000000000001, + 24.001000000000001, + 25.001000000000001, + 26.001000000000001, + 27.001000000000001, + 28.001000000000001, + 29.001000000000001, + 30.001000000000001, + 31.001000000000001, + 32.000999999999998, + 33.000999999999998, + 34.000999999999998, + 35.000999999999998, + 36.000999999999998, + 37.000999999999998, + 38.000999999999998, + 39.000999999999998, + 40.000999999999998, + 41.000999999999998, + 42.000999999999998, + 43.000999999999998, + 44.000999999999998, + 45.000999999999998, + 46.000999999999998, + 47.000999999999998, + 48.000999999999998, + 49.000999999999998, + 50.000999999999998, + 51.000999999999998, + 52.000999999999998, + 53.000999999999998, + 54.000999999999998, + 55.000999999999998, + 56.000999999999998, + 57.000999999999998, + 58.000999999999998, + 59.000999999999998, + 60.000999999999998, + 61.000999999999998, + 62.000999999999998, + 63.000999999999998, + 64.001000000000005, + 65.001000000000005, + 66.001000000000005, + 67.001000000000005, + 68.001000000000005, + 69.001000000000005, + 70.001000000000005, + 71.001000000000005, + 72.001000000000005, + 73.001000000000005, + 74.001000000000005, + 75.001000000000005, + 76.001000000000005, + 77.001000000000005, + 78.001000000000005, + 79.001000000000005, + 80.001000000000005, + 81.001000000000005, + 82.001000000000005, + 83.001000000000005, + 84.001000000000005, + 85.001000000000005, + 86.001000000000005, + 87.001000000000005, + 88.001000000000005, + 89.001000000000005, + 90.001000000000005, + 91.001000000000005, + 92.001000000000005, + 93.001000000000005, + 94.001000000000005, + 95.001000000000005, + 96.001000000000005, + 97.001000000000005, + 98.001000000000005, + 99.001000000000005, + 100.001, + 101.001, + 102.001, + 103.001, + 104.001, + 105.001, + 106.001, + 107.001, + 108.001, + 109.001, + 110.001, + 111.001, + 112.001, + 113.001, + 114.001, + 115.001, + 116.001, + 117.001, + 118.001, + 119.001, + 120.001, + 121.001, + 122.001, + 123.001, + 124.001, + 125.001, + 126.001, + 127.001, + 128.001, + 129.001, + 130.001, + 131.001, + 132.001, + 133.001, + 134.001, + 135.001, + 136.001, + 137.001, + 138.001, + 139.001, + 140.001, + 141.001, + 142.001, + 143.001, + 144.001, + 145.001, + 146.001, + 147.001, + 148.001, + 149.001, + 150.001, + 151.001, + 152.001, + 153.001, + 154.001, + 155.001, + 156.001, + 157.001, + 158.001, + 159.001, + 160.001, + 161.001, + 162.001, + 163.001, + 164.001, + 165.001, + 166.001, + 167.001, + 168.001, + 169.001, + 170.001, + 171.001, + 172.001, + 173.001, + 174.001, + 175.001, + 176.001, + 177.001, + 178.001, + 179.001, + 180.001, + 181.001, + 182.001, + 183.001, + 184.001, + 185.001, + 186.001, + 187.001, + 188.001, + 189.001, + 190.001, + 191.001, + 192.001, + 193.001, + 194.001, + 195.001, + 196.001, + 197.001, + 198.001, + 199.001, + 200.001, + 201.001, + 202.001, + 203.001, + 204.001, + 205.001, + 206.001, + 207.001, + 208.001, + 209.001, + 210.001, + 211.001, + 212.001, + 213.001, + 214.001, + 215.001, + 216.001, + 217.001, + 218.001, + 219.001, + 220.001, + 221.001, + 222.001, + 223.001, + 224.001, + 225.001, + 226.001, + 227.001, + 228.001, + 229.001, + 230.001, + 231.001, + 232.001, + 233.001, + 234.001, + 235.001, + 236.001, + 237.001, + 238.001, + 239.001, + 240.001, + 241.001, + 242.001, + 243.001, + 244.001, + 245.001, + 246.001, + 247.001, + 248.001, + 249.001, + 250.001, + 251.001, + 252.001, + 253.001, + 254.001, + 255.001, + 256.00099999999998, + 257.00099999999998, + 258.00099999999998, + 259.00099999999998, + 260.00099999999998, + 261.00099999999998, + 262.00099999999998, + 263.00099999999998, + 264.00099999999998, + 265.00099999999998, + 266.00099999999998, + 267.00099999999998, + 268.00099999999998, + 269.00099999999998, + 270.00099999999998, + 271.00099999999998, + 272.00099999999998, + 273.00099999999998, + 274.00099999999998, + 275.00099999999998, + 276.00099999999998, + 277.00099999999998, + 278.00099999999998, + 279.00099999999998, + 280.00099999999998, + 281.00099999999998, + 282.00099999999998, + 283.00099999999998, + 284.00099999999998, + 285.00099999999998, + 286.00099999999998, + 287.00099999999998, + 288.00099999999998, + 289.00099999999998, + 290.00099999999998, + 291.00099999999998, + 292.00099999999998, + 293.00099999999998, + 294.00099999999998, + 295.00099999999998, + 296.00099999999998, + 297.00099999999998, + 298.00099999999998, + 299.00099999999998, + 300.00099999999998, + 301.00099999999998, + 302.00099999999998, + 303.00099999999998, + 304.00099999999998, + 305.00099999999998, + 306.00099999999998, + 307.00099999999998, + 308.00099999999998, + 309.00099999999998, + 310.00099999999998, + 311.00099999999998, + 312.00099999999998, + 313.00099999999998, + 314.00099999999998, + 315.00099999999998, + 316.00099999999998, + 317.00099999999998, + 318.00099999999998, + 319.00099999999998, + 320.00099999999998, + 321.00099999999998, + 322.00099999999998, + 323.00099999999998, + 324.00099999999998, + 325.00099999999998, + 326.00099999999998, + 327.00099999999998, + 328.00099999999998, + 329.00099999999998, + 330.00099999999998, + 331.00099999999998, + 332.00099999999998, + 333.00099999999998, + 334.00099999999998, + 335.00099999999998, + 336.00099999999998, + 337.00099999999998, + 338.00099999999998, + 339.00099999999998, + 340.00099999999998, + 341.00099999999998, + 342.00099999999998, + 343.00099999999998, + 344.00099999999998, + 345.00099999999998, + 346.00099999999998, + 347.00099999999998, + 348.00099999999998, + 349.00099999999998, + 350.00099999999998, + 351.00099999999998, + 352.00099999999998, + 353.00099999999998, + 354.00099999999998, + 355.00099999999998, + 356.00099999999998, + 357.00099999999998, + 358.00099999999998, + 359.00099999999998, + 360.00099999999998, + 361.00099999999998, + 362.00099999999998, + 363.00099999999998, + 364.00099999999998, + 365.00099999999998, + 366.00099999999998, + 367.00099999999998, + 368.00099999999998, + 369.00099999999998, + 370.00099999999998, + 371.00099999999998, + 372.00099999999998, + 373.00099999999998, + 374.00099999999998, + 375.00099999999998, + 376.00099999999998, + 377.00099999999998, + 378.00099999999998, + 379.00099999999998, + 380.00099999999998, + 381.00099999999998, + 382.00099999999998, + 383.00099999999998, + 384.00099999999998, + 385.00099999999998, + 386.00099999999998, + 387.00099999999998, + 388.00099999999998, + 389.00099999999998, + 390.00099999999998, + 391.00099999999998, + 392.00099999999998, + 393.00099999999998, + 394.00099999999998, + 395.00099999999998, + 396.00099999999998, + 397.00099999999998, + 398.00099999999998, + 399.00099999999998, + 400.00099999999998, + 401.00099999999998, + 402.00099999999998, + 403.00099999999998, + 404.00099999999998, + 405.00099999999998, + 406.00099999999998, + 407.00099999999998, + 408.00099999999998, + 409.00099999999998, + 410.00099999999998, + 411.00099999999998, + 412.00099999999998, + 413.00099999999998, + 414.00099999999998, + 415.00099999999998, + 416.00099999999998, + 417.00099999999998, + 418.00099999999998, + 419.00099999999998, + 420.00099999999998, + 421.00099999999998, + 422.00099999999998, + 423.00099999999998, + 424.00099999999998, + 425.00099999999998, + 426.00099999999998, + 427.00099999999998, + 428.00099999999998, + 429.00099999999998, + 430.00099999999998, + 431.00099999999998, + 432.00099999999998, + 433.00099999999998, + 434.00099999999998, + 435.00099999999998, + 436.00099999999998, + 437.00099999999998, + 438.00099999999998, + 439.00099999999998, + 440.00099999999998, + 441.00099999999998, + 442.00099999999998, + 443.00099999999998, + 444.00099999999998, + 445.00099999999998, + 446.00099999999998, + 447.00099999999998, + 448.00099999999998, + 449.00099999999998, + 450.00099999999998, + 451.00099999999998, + 452.00099999999998, + 453.00099999999998, + 454.00099999999998, + 455.00099999999998, + 456.00099999999998, + 457.00099999999998, + 458.00099999999998, + 459.00099999999998, + 460.00099999999998, + 461.00099999999998, + 462.00099999999998, + 463.00099999999998, + 464.00099999999998, + 465.00099999999998, + 466.00099999999998, + 467.00099999999998, + 468.00099999999998, + 469.00099999999998, + 470.00099999999998, + 471.00099999999998, + 472.00099999999998, + 473.00099999999998, + 474.00099999999998, + 475.00099999999998, + 476.00099999999998, + 477.00099999999998, + 478.00099999999998, + 479.00099999999998, + 480.00099999999998, + 481.00099999999998, + 482.00099999999998, + 483.00099999999998, + 484.00099999999998, + 485.00099999999998, + 486.00099999999998, + 487.00099999999998, + 488.00099999999998, + 489.00099999999998, + 490.00099999999998, + 491.00099999999998, + 492.00099999999998, + 493.00099999999998, + 494.00099999999998, + 495.00099999999998, + 496.00099999999998, + 497.00099999999998, + 498.00099999999998, + 499.00099999999998, + 500.00099999999998, + 501.00099999999998, + 502.00099999999998, + 503.00099999999998, + 504.00099999999998, + 505.00099999999998, + 506.00099999999998, + 507.00099999999998, + 508.00099999999998, + 509.00099999999998, + 510.00099999999998, + 511.00099999999998, + 512.00099999999998, + 513.00099999999998, + 514.00099999999998, + 515.00099999999998, + 516.00099999999998, + 517.00099999999998, + 518.00099999999998, + 519.00099999999998, + 520.00099999999998, + 521.00099999999998, + 522.00099999999998, + 523.00099999999998, + 524.00099999999998, + 525.00099999999998, + 526.00099999999998, + 527.00099999999998, + 528.00099999999998, + 529.00099999999998, + 530.00099999999998, + 531.00099999999998, + 532.00099999999998, + 533.00099999999998, + 534.00099999999998, + 535.00099999999998, + 536.00099999999998, + 537.00099999999998, + 538.00099999999998, + 539.00099999999998, + 540.00099999999998, + 541.00099999999998, + 542.00099999999998, + 543.00099999999998, + 544.00099999999998, + 545.00099999999998, + 546.00099999999998, + 547.00099999999998, + 548.00099999999998, + 549.00099999999998, + 550.00099999999998, + 551.00099999999998, + 552.00099999999998, + 553.00099999999998, + 554.00099999999998, + 555.00099999999998, + 556.00099999999998, + 557.00099999999998, + 558.00099999999998, + 559.00099999999998, + 560.00099999999998, + 561.00099999999998, + 562.00099999999998, + 563.00099999999998, + 564.00099999999998, + 565.00099999999998, + 566.00099999999998, + 567.00099999999998, + 568.00099999999998, + 569.00099999999998, + 570.00099999999998, + 571.00099999999998, + 572.00099999999998, + 573.00099999999998, + 574.00099999999998, + 575.00099999999998, + 576.00099999999998, + 577.00099999999998, + 578.00099999999998, + 579.00099999999998, + 580.00099999999998, + 581.00099999999998, + 582.00099999999998, + 583.00099999999998, + 584.00099999999998, + 585.00099999999998, + 586.00099999999998, + 587.00099999999998, + 588.00099999999998, + 589.00099999999998, + 590.00099999999998, + 591.00099999999998, + 592.00099999999998, + 593.00099999999998, + 594.00099999999998, + 595.00099999999998, + 596.00099999999998, + 597.00099999999998, + 598.00099999999998, + 599.00099999999998, + 600.00099999999998, + 601.00099999999998, + 602.00099999999998, + 603.00099999999998, + 604.00099999999998, + 605.00099999999998, + 606.00099999999998, + 607.00099999999998, + 608.00099999999998, + 609.00099999999998, + 610.00099999999998, + 611.00099999999998, + 612.00099999999998, + 613.00099999999998, + 614.00099999999998, + 615.00099999999998, + 616.00099999999998, + 617.00099999999998, + 618.00099999999998, + 619.00099999999998, + 620.00099999999998, + 621.00099999999998, + 622.00099999999998, + 623.00099999999998, + 624.00099999999998, + 625.00099999999998, + 626.00099999999998, + 627.00099999999998, + 628.00099999999998, + 629.00099999999998, + 630.00099999999998, + 631.00099999999998, + 632.00099999999998, + 633.00099999999998, + 634.00099999999998, + 635.00099999999998, + 636.00099999999998, + 637.00099999999998, + 638.00099999999998, + 639.00099999999998, + 640.00099999999998, + 641.00099999999998, + 642.00099999999998, + 643.00099999999998, + 644.00099999999998, + 645.00099999999998, + 646.00099999999998, + 647.00099999999998, + 648.00099999999998, + 649.00099999999998, + 650.00099999999998, + 651.00099999999998, + 652.00099999999998, + 653.00099999999998, + 654.00099999999998, + 655.00099999999998, + 656.00099999999998, + 657.00099999999998, + 658.00099999999998, + 659.00099999999998, + 660.00099999999998, + 661.00099999999998, + 662.00099999999998, + 663.00099999999998, + 664.00099999999998, + 665.00099999999998, + 666.00099999999998, + 667.00099999999998, + 668.00099999999998, + 669.00099999999998, + 670.00099999999998, + 671.00099999999998, + 672.00099999999998, + 673.00099999999998, + 674.00099999999998, + 675.00099999999998, + 676.00099999999998, + 677.00099999999998, + 678.00099999999998, + 679.00099999999998, + 680.00099999999998, + 681.00099999999998, + 682.00099999999998, + 683.00099999999998, + 684.00099999999998, + 685.00099999999998, + 686.00099999999998, + 687.00099999999998, + 688.00099999999998, + 689.00099999999998, + 690.00099999999998, + 691.00099999999998, + 692.00099999999998, + 693.00099999999998, + 694.00099999999998, + 695.00099999999998, + 696.00099999999998, + 697.00099999999998, + 698.00099999999998, + 699.00099999999998, + 700.00099999999998, + 701.00099999999998, + 702.00099999999998, + 703.00099999999998, + 704.00099999999998, + 705.00099999999998, + 706.00099999999998, + 707.00099999999998, + 708.00099999999998, + 709.00099999999998, + 710.00099999999998, + 711.00099999999998, + 712.00099999999998, + 713.00099999999998, + 714.00099999999998, + 715.00099999999998, + 716.00099999999998, + 717.00099999999998, + 718.00099999999998, + 719.00099999999998, + 720.00099999999998, + 721.00099999999998, + 722.00099999999998, + 723.00099999999998, + 724.00099999999998, + 725.00099999999998, + 726.00099999999998, + 727.00099999999998, + 728.00099999999998, + 729.00099999999998, + 730.00099999999998, + 731.00099999999998, + 732.00099999999998, + 733.00099999999998, + 734.00099999999998, + 735.00099999999998, + 736.00099999999998, + 737.00099999999998, + 738.00099999999998, + 739.00099999999998, + 740.00099999999998, + 741.00099999999998, + 742.00099999999998, + 743.00099999999998, + 744.00099999999998, + 745.00099999999998, + 746.00099999999998, + 747.00099999999998, + 748.00099999999998, + 749.00099999999998, + 750.00099999999998, + 751.00099999999998, + 752.00099999999998, + 753.00099999999998, + 754.00099999999998, + 755.00099999999998, + 756.00099999999998, + 757.00099999999998, + 758.00099999999998, + 759.00099999999998, + 760.00099999999998, + 761.00099999999998, + 762.00099999999998, + 763.00099999999998, + 764.00099999999998, + 765.00099999999998, + 766.00099999999998, + 767.00099999999998, + 768.00099999999998, + 769.00099999999998, + 770.00099999999998, + 771.00099999999998, + 772.00099999999998, + 773.00099999999998, + 774.00099999999998, + 775.00099999999998, + 776.00099999999998, + 777.00099999999998, + 778.00099999999998, + 779.00099999999998, + 780.00099999999998, + 781.00099999999998, + 782.00099999999998, + 783.00099999999998, + 784.00099999999998, + 785.00099999999998, + 786.00099999999998, + 787.00099999999998, + 788.00099999999998, + 789.00099999999998, + 790.00099999999998, + 791.00099999999998, + 792.00099999999998, + 793.00099999999998, + 794.00099999999998, + 795.00099999999998, + 796.00099999999998, + 797.00099999999998, + 798.00099999999998, + 799.00099999999998, + 800.00099999999998, + 801.00099999999998, + 802.00099999999998, + 803.00099999999998, + 804.00099999999998, + 805.00099999999998, + 806.00099999999998, + 807.00099999999998, + 808.00099999999998, + 809.00099999999998, + 810.00099999999998, + 811.00099999999998, + 812.00099999999998, + 813.00099999999998, + 814.00099999999998, + 815.00099999999998, + 816.00099999999998, + 817.00099999999998, + 818.00099999999998, + 819.00099999999998, + 820.00099999999998, + 821.00099999999998, + 822.00099999999998, + 823.00099999999998, + 824.00099999999998, + 825.00099999999998, + 826.00099999999998, + 827.00099999999998, + 828.00099999999998, + 829.00099999999998, + 830.00099999999998, + 831.00099999999998, + 832.00099999999998, + 833.00099999999998, + 834.00099999999998, + 835.00099999999998, + 836.00099999999998, + 837.00099999999998, + 838.00099999999998, + 839.00099999999998, + 840.00099999999998, + 841.00099999999998, + 842.00099999999998, + 843.00099999999998, + 844.00099999999998, + 845.00099999999998, + 846.00099999999998, + 847.00099999999998, + 848.00099999999998, + 849.00099999999998, + 850.00099999999998, + 851.00099999999998, + 852.00099999999998, + 853.00099999999998, + 854.00099999999998, + 855.00099999999998, + 856.00099999999998, + 857.00099999999998, + 858.00099999999998, + 859.00099999999998, + 860.00099999999998, + 861.00099999999998, + 862.00099999999998, + 863.00099999999998, + 864.00099999999998, + 865.00099999999998, + 866.00099999999998, + 867.00099999999998, + 868.00099999999998, + 869.00099999999998, + 870.00099999999998, + 871.00099999999998, + 872.00099999999998, + 873.00099999999998, + 874.00099999999998, + 875.00099999999998, + 876.00099999999998, + 877.00099999999998, + 878.00099999999998, + 879.00099999999998, + 880.00099999999998, + 881.00099999999998, + 882.00099999999998, + 883.00099999999998, + 884.00099999999998, + 885.00099999999998, + 886.00099999999998, + 887.00099999999998, + 888.00099999999998, + 889.00099999999998, + 890.00099999999998, + 891.00099999999998, + 892.00099999999998, + 893.00099999999998, + 894.00099999999998, + 895.00099999999998, + 896.00099999999998, + 897.00099999999998, + 898.00099999999998, + 899.00099999999998, + 900.00099999999998, + 901.00099999999998, + 902.00099999999998, + 903.00099999999998, + 904.00099999999998, + 905.00099999999998, + 906.00099999999998, + 907.00099999999998, + 908.00099999999998, + 909.00099999999998, + 910.00099999999998, + 911.00099999999998, + 912.00099999999998, + 913.00099999999998, + 914.00099999999998, + 915.00099999999998, + 916.00099999999998, + 917.00099999999998, + 918.00099999999998, + 919.00099999999998, + 920.00099999999998, + 921.00099999999998, + 922.00099999999998, + 923.00099999999998, + 924.00099999999998, + 925.00099999999998, + 926.00099999999998, + 927.00099999999998, + 928.00099999999998, + 929.00099999999998, + 930.00099999999998, + 931.00099999999998, + 932.00099999999998, + 933.00099999999998, + 934.00099999999998, + 935.00099999999998, + 936.00099999999998, + 937.00099999999998, + 938.00099999999998, + 939.00099999999998, + 940.00099999999998, + 941.00099999999998, + 942.00099999999998, + 943.00099999999998, + 944.00099999999998, + 945.00099999999998, + 946.00099999999998, + 947.00099999999998, + 948.00099999999998, + 949.00099999999998, + 950.00099999999998, + 951.00099999999998, + 952.00099999999998, + 953.00099999999998, + 954.00099999999998, + 955.00099999999998, + 956.00099999999998, + 957.00099999999998, + 958.00099999999998, + 959.00099999999998, + 960.00099999999998, + 961.00099999999998, + 962.00099999999998, + 963.00099999999998, + 964.00099999999998, + 965.00099999999998, + 966.00099999999998, + 967.00099999999998, + 968.00099999999998, + 969.00099999999998, + 970.00099999999998, + 971.00099999999998, + 972.00099999999998, + 973.00099999999998, + 974.00099999999998, + 975.00099999999998, + 976.00099999999998, + 977.00099999999998, + 978.00099999999998, + 979.00099999999998, + 980.00099999999998, + 981.00099999999998, + 982.00099999999998, + 983.00099999999998, + 984.00099999999998, + 985.00099999999998, + 986.00099999999998, + 987.00099999999998, + 988.00099999999998, + 989.00099999999998, + 990.00099999999998, + 991.00099999999998, + 992.00099999999998, + 993.00099999999998, + 994.00099999999998, + 995.00099999999998, + 996.00099999999998, + 997.00099999999998, + 998.00099999999998, + 999.00099999999998, + 1020, + 1041, + 1062, + 1083, + 1104, + 1125, + 1146, + 1167, + 1188, + 1209, + 1230, + 1251, + 1272, + 1293, + 1314, + 1335, + 1356, + 1377, + 1398, + 1419, + 1440, + 1461, + 1482, + 1503, + 1524, + 1545, + 1566, + 1587, + 1608, + 1629, + 1650, + 1671, + 1692, + 1713, + 1734, + 1755, + 1776, + 1797, + 1818, + 1839, + 1860, + 1881, + 1902, + 1923, + 1944, + 1965, + 1986, + 2007, + 2028, + 2049, + 2070, + 2091, + 2112, + 2133, + 2154, + 2175, + 2196, + 2217, + 2238, + 2259, + 2280, + 2301, + 2322, + 2343, + 2364, + 2385, + 2406 + ] + }, + "name" : "PionRange", + "type" : "LinterpFunction" + }, + { + "data" : + { + "coords" : + [ + 5.7325200000000003e-06, + 0.0044293700000000002, + 0.0137278, + 0.0271325, + 0.044324700000000002, + 0.065095600000000003, + 0.089192900000000006, + 0.116588, + 0.14719099999999999, + 0.180927, + 0.217754, + 0.25755, + 0.30003600000000002, + 0.34545199999999998, + 0.39390700000000001, + 0.44515700000000002, + 0.49914599999999998, + 0.55570900000000001, + 0.61490100000000003, + 0.67686100000000005, + 0.74131599999999997, + 0.80841099999999999, + 0.87808399999999998, + 0.950187, + 1.0248699999999999, + 1.1019399999999999, + 1.18144, + 1.2634099999999999, + 1.34768, + 1.43435, + 1.52335, + 1.6146100000000001, + 1.7082299999999999, + 1.80406, + 1.9021399999999999, + 2.0024799999999998, + 2.1049600000000002, + 2.20967, + 2.3165200000000001, + 2.4254899999999999, + 2.5366200000000001, + 2.64981, + 2.76511, + 2.8824900000000002, + 3.0018799999999999, + 3.1233599999999999, + 3.24682, + 3.3722799999999999, + 3.4997600000000002, + 3.6291799999999999, + 3.7605599999999999, + 3.8938899999999999, + 4.0291199999999998, + 4.1662800000000004, + 4.3050699999999997, + 4.4454500000000001, + 4.5874600000000001, + 4.7311300000000003, + 4.8765000000000001, + 5.0236099999999997, + 5.1725099999999999, + 5.3232400000000002, + 5.4758500000000003, + 5.6303700000000001, + 5.7868700000000004, + 5.9453899999999997, + 6.1059700000000001, + 6.2681899999999997, + 6.4318999999999997, + 6.5971200000000003, + 6.7638800000000003, + 6.9321999999999999, + 7.1021200000000002, + 7.2736700000000001, + 7.4468699999999997, + 7.6217699999999997, + 7.7983900000000004, + 7.9767700000000001, + 8.1569500000000001, + 8.3389500000000005, + 8.5225299999999997, + 8.7074800000000003, + 8.8938299999999995, + 9.0815900000000003, + 9.2707999999999995, + 9.4614700000000003, + 9.6536200000000001, + 9.8472899999999992, + 10.0425, + 10.2392, + 10.4376, + 10.637499999999999, + 10.8391, + 11.042199999999999, + 11.246499999999999, + 11.452199999999999, + 11.6591, + 11.8674, + 12.077, + 12.2881, + 12.500400000000001, + 12.7142, + 12.929500000000001, + 13.146100000000001, + 13.3642, + 13.5838, + 13.8048, + 14.026999999999999, + 14.250400000000001, + 14.475, + 14.700799999999999, + 14.927899999999999, + 15.1562, + 15.3858, + 15.6167, + 15.8489, + 16.0824, + 16.3172, + 16.5534, + 16.790800000000001, + 17.029399999999999, + 17.269100000000002, + 17.510000000000002, + 17.751899999999999, + 17.995100000000001, + 18.2393, + 18.4848, + 18.731400000000001, + 18.979299999999999, + 19.228300000000001, + 19.4785, + 19.73, + 19.982700000000001, + 20.236499999999999, + 20.491299999999999, + 20.7471, + 21.004000000000001, + 21.262, + 21.521000000000001, + 21.781099999999999, + 22.042300000000001, + 22.304600000000001, + 22.568000000000001, + 22.8325, + 23.098099999999999, + 23.364899999999999, + 23.6326, + 23.901399999999999, + 24.171099999999999, + 24.441700000000001, + 24.7134, + 24.986000000000001, + 25.259699999999999, + 25.534300000000002, + 25.809999999999999, + 26.0867, + 26.3644, + 26.6431, + 26.922899999999998, + 27.203600000000002, + 27.485099999999999, + 27.767499999999998, + 28.050699999999999, + 28.334800000000001, + 28.619700000000002, + 28.9055, + 29.1921, + 29.479600000000001, + 29.768000000000001, + 30.057300000000001, + 30.3474, + 30.638500000000001, + 30.930399999999999, + 31.223199999999999, + 31.516999999999999, + 31.811699999999998, + 32.107300000000002, + 32.403799999999997, + 32.7012, + 32.999600000000001, + 33.298900000000003, + 33.599200000000003, + 33.900399999999998, + 34.202599999999997, + 34.505800000000001, + 34.809800000000003, + 35.114600000000003, + 35.420099999999998, + 35.726300000000002, + 36.033299999999997, + 36.341000000000001, + 36.649500000000003, + 36.9587, + 37.268599999999999, + 37.5794, + 37.890799999999999, + 38.203099999999999, + 38.516100000000002, + 38.829900000000002, + 39.144399999999997, + 39.459800000000001, + 39.7759, + 40.092799999999997, + 40.410499999999999, + 40.728999999999999, + 41.048299999999998, + 41.368400000000001, + 41.689300000000003, + 42.011000000000003, + 42.333500000000001, + 42.6569, + 42.981000000000002, + 43.305799999999998, + 43.631300000000003, + 43.9574, + 44.284100000000002, + 44.611499999999999, + 44.939500000000002, + 45.268300000000004, + 45.5976, + 45.927700000000002, + 46.258400000000002, + 46.589700000000001, + 46.921799999999998, + 47.2545, + 47.587899999999998, + 47.921999999999997, + 48.256700000000002, + 48.592199999999998, + 48.9283, + 49.265099999999997, + 49.602600000000002, + 49.940800000000003, + 50.279699999999998, + 50.619399999999999, + 50.959699999999998, + 51.300699999999999, + 51.642400000000002, + 51.9848, + 52.3277, + 52.671199999999999, + 53.0152, + 53.3598, + 53.704999999999998, + 54.050800000000002, + 54.397100000000002, + 54.744100000000003, + 55.0916, + 55.439700000000002, + 55.788400000000003, + 56.137599999999999, + 56.487499999999997, + 56.837899999999998, + 57.189, + 57.540599999999998, + 57.892899999999997, + 58.245699999999999, + 58.599200000000003, + 58.953200000000002, + 59.307899999999997, + 59.6631, + 60.018999999999998, + 60.375500000000002, + 60.732599999999998, + 61.090200000000003, + 61.448399999999999, + 61.807099999999998, + 62.166200000000003, + 62.5259, + 62.886000000000003, + 63.246699999999997, + 63.607900000000001, + 63.969499999999996, + 64.331699999999998, + 64.694400000000002, + 65.057599999999994, + 65.421300000000002, + 65.785499999999999, + 66.150300000000001, + 66.515500000000003, + 66.881299999999996, + 67.247600000000006, + 67.614400000000003, + 67.981700000000004, + 68.349599999999995, + 68.718000000000004, + 69.0869, + 69.456299999999999, + 69.826300000000003, + 70.196799999999996, + 70.567800000000005, + 70.9392, + 71.311099999999996, + 71.683499999999995, + 72.056299999999993, + 72.429500000000004, + 72.803100000000001, + 73.177300000000002, + 73.5518, + 73.9268, + 74.302300000000002, + 74.678200000000004, + 75.054500000000004, + 75.431299999999993, + 75.808599999999998, + 76.186300000000003, + 76.564499999999995, + 76.943100000000001, + 77.322199999999995, + 77.701700000000002, + 78.081699999999998, + 78.462199999999996, + 78.843100000000007, + 79.224500000000006, + 79.606300000000005, + 79.988600000000005, + 80.371399999999994, + 80.754599999999996, + 81.138099999999994, + 81.522099999999995, + 81.906400000000005, + 82.291200000000003, + 82.676299999999998, + 83.061800000000005, + 83.447800000000001, + 83.834100000000007, + 84.220799999999997, + 84.607900000000001, + 84.995400000000004, + 85.383300000000006, + 85.771600000000007, + 86.160399999999996, + 86.549499999999995, + 86.938999999999993, + 87.328900000000004, + 87.719200000000001, + 88.109899999999996, + 88.501099999999994, + 88.892600000000002, + 89.284599999999998, + 89.676900000000003, + 90.069699999999997, + 90.462800000000001, + 90.856399999999994, + 91.250299999999996, + 91.644499999999994, + 92.039100000000005, + 92.433999999999997, + 92.829300000000003, + 93.224999999999994, + 93.620900000000006, + 94.017300000000006, + 94.413899999999998, + 94.811000000000007, + 95.208399999999997, + 95.606099999999998, + 96.004199999999997, + 96.402600000000007, + 96.801400000000001, + 97.200599999999994, + 97.600099999999998, + 97.999899999999997, + 98.400099999999995, + 98.800700000000006, + 99.201700000000002, + 99.602900000000005, + 100.005, + 100.407, + 100.809, + 101.212, + 101.61499999999999, + 102.018, + 102.422, + 102.82599999999999, + 103.23, + 103.634, + 104.039, + 104.444, + 104.84999999999999, + 105.256, + 105.66200000000001, + 106.068, + 106.47499999999999, + 106.88200000000001, + 107.289, + 107.697, + 108.104, + 108.51300000000001, + 108.92100000000001, + 109.33, + 109.739, + 110.149, + 110.55800000000001, + 110.968, + 111.379, + 111.79000000000001, + 112.20099999999999, + 112.61199999999999, + 113.023, + 113.435, + 113.84699999999999, + 114.259, + 114.672, + 115.08499999999999, + 115.498, + 115.91200000000001, + 116.325, + 116.739, + 117.15300000000001, + 117.568, + 117.983, + 118.398, + 118.813, + 119.229, + 119.645, + 120.06100000000001, + 120.477, + 120.89400000000001, + 121.31100000000001, + 121.72799999999999, + 122.146, + 122.563, + 122.982, + 123.40000000000001, + 123.818, + 124.23699999999999, + 124.65600000000001, + 125.07599999999999, + 125.495, + 125.91500000000001, + 126.33499999999999, + 126.755, + 127.176, + 127.596, + 128.017, + 128.43899999999999, + 128.86000000000001, + 129.28200000000001, + 129.70400000000001, + 130.126, + 130.548, + 130.971, + 131.39400000000001, + 131.81700000000001, + 132.24100000000001, + 132.66399999999999, + 133.08799999999999, + 133.512, + 133.93700000000001, + 134.36199999999999, + 134.786, + 135.21100000000001, + 135.637, + 136.06200000000001, + 136.488, + 136.91399999999999, + 137.34, + 137.767, + 138.19300000000001, + 138.62, + 139.047, + 139.47499999999999, + 139.90199999999999, + 140.33000000000001, + 140.75800000000001, + 141.18600000000001, + 141.614, + 142.04300000000001, + 142.47200000000001, + 142.90100000000001, + 143.33000000000001, + 143.75999999999999, + 144.19, + 144.62, + 145.05000000000001, + 145.47999999999999, + 145.911, + 146.34200000000001, + 146.773, + 147.20400000000001, + 147.63499999999999, + 148.06700000000001, + 148.49799999999999, + 148.93000000000001, + 149.36199999999999, + 149.79499999999999, + 150.227, + 150.66, + 151.09299999999999, + 151.52600000000001, + 151.959, + 152.392, + 152.82599999999999, + 153.25999999999999, + 153.69399999999999, + 154.12799999999999, + 154.56200000000001, + 154.99700000000001, + 155.43199999999999, + 155.86699999999999, + 156.30199999999999, + 156.73699999999999, + 157.173, + 157.608, + 158.04400000000001, + 158.47999999999999, + 158.916, + 159.35300000000001, + 159.78899999999999, + 160.226, + 160.66300000000001, + 161.09999999999999, + 161.53700000000001, + 161.97399999999999, + 162.41200000000001, + 162.84899999999999, + 163.28700000000001, + 163.72499999999999, + 164.16399999999999, + 164.602, + 165.041, + 165.47900000000001, + 165.91800000000001, + 166.357, + 166.797, + 167.23599999999999, + 167.67599999999999, + 168.11500000000001, + 168.55500000000001, + 168.995, + 169.435, + 169.875, + 170.316, + 170.756, + 171.196, + 171.637, + 172.077, + 172.518, + 172.959, + 173.40000000000001, + 173.84100000000001, + 174.28200000000001, + 174.72300000000001, + 175.16399999999999, + 175.60499999999999, + 176.047, + 176.488, + 176.93000000000001, + 177.37100000000001, + 177.81299999999999, + 178.255, + 178.697, + 179.13900000000001, + 179.58099999999999, + 180.023, + 180.465, + 180.90799999999999, + 181.34999999999999, + 181.79300000000001, + 182.23500000000001, + 182.678, + 183.12100000000001, + 183.56399999999999, + 184.00700000000001, + 184.44999999999999, + 184.893, + 185.33699999999999, + 185.78, + 186.22300000000001, + 186.667, + 187.11099999999999, + 187.554, + 187.99799999999999, + 188.44200000000001, + 188.886, + 189.33000000000001, + 189.774, + 190.21899999999999, + 190.66300000000001, + 191.107, + 191.55199999999999, + 191.99700000000001, + 192.441, + 192.886, + 193.33099999999999, + 193.77600000000001, + 194.221, + 194.666, + 195.11199999999999, + 195.55699999999999, + 196.00299999999999, + 196.44800000000001, + 196.89400000000001, + 197.339, + 197.785, + 198.23099999999999, + 198.67699999999999, + 199.12299999999999, + 199.56999999999999, + 200.01599999999999, + 200.46199999999999, + 200.90899999999999, + 201.35499999999999, + 201.80199999999999, + 202.249, + 202.696, + 203.142, + 203.59, + 204.03700000000001, + 204.48400000000001, + 204.93100000000001, + 205.37899999999999, + 205.82599999999999, + 206.274, + 206.721, + 207.16900000000001, + 207.61699999999999, + 208.065, + 208.51300000000001, + 208.96100000000001, + 209.40899999999999, + 209.858, + 210.30600000000001, + 210.755, + 211.203, + 211.65199999999999, + 212.101, + 212.55000000000001, + 212.999, + 213.44800000000001, + 213.89699999999999, + 214.346, + 214.79499999999999, + 215.245, + 215.69399999999999, + 216.14400000000001, + 216.59399999999999, + 217.04300000000001, + 217.49299999999999, + 217.94300000000001, + 218.39400000000001, + 218.84399999999999, + 219.29400000000001, + 219.744, + 220.19499999999999, + 220.64500000000001, + 221.096, + 221.547, + 221.99799999999999, + 222.44900000000001, + 222.90000000000001, + 223.351, + 223.80199999999999, + 224.25299999999999, + 224.70500000000001, + 225.15600000000001, + 225.608, + 226.059, + 226.511, + 226.96299999999999, + 227.41499999999999, + 227.86699999999999, + 228.31899999999999, + 228.77199999999999, + 229.22399999999999, + 229.67599999999999, + 230.12899999999999, + 230.58199999999999, + 231.03399999999999, + 231.48699999999999, + 231.94, + 232.393, + 232.846, + 233.29900000000001, + 233.75299999999999, + 234.20599999999999, + 234.66, + 235.113, + 235.56700000000001, + 236.02099999999999, + 236.47399999999999, + 236.928, + 237.38200000000001, + 237.83699999999999, + 238.291, + 238.745, + 239.19999999999999, + 239.654, + 240.10900000000001, + 240.56399999999999, + 241.018, + 241.47300000000001, + 241.928, + 242.38300000000001, + 242.839, + 243.29400000000001, + 243.749, + 244.20500000000001, + 244.66, + 245.11600000000001, + 245.572, + 246.02799999999999, + 246.48400000000001, + 246.94, + 247.39599999999999, + 247.852, + 248.309, + 248.76499999999999, + 249.22200000000001, + 249.678, + 250.13499999999999, + 250.59200000000001, + 251.04900000000001, + 251.506, + 251.96299999999999, + 252.41999999999999, + 252.87700000000001, + 253.33500000000001, + 253.792, + 254.25, + 254.708, + 255.16499999999999, + 255.62299999999999, + 256.08100000000002, + 256.53899999999999, + 256.99799999999999, + 257.45600000000002, + 257.91399999999999, + 258.37299999999999, + 258.83100000000002, + 259.29000000000002, + 259.74900000000002, + 260.20800000000003, + 260.66699999999997, + 261.12599999999998, + 261.58499999999998, + 262.04399999999998, + 262.50299999999999, + 262.96300000000002, + 263.42200000000003, + 263.88200000000001, + 264.34199999999998, + 264.80200000000002, + 265.262, + 265.72199999999998, + 266.18200000000002, + 266.642, + 267.10199999999998, + 267.56299999999999, + 268.02300000000002, + 268.48399999999998, + 268.94499999999999, + 269.40499999999997, + 269.86599999999999, + 270.327, + 270.78800000000001, + 271.25, + 271.71100000000001, + 272.17200000000003, + 272.63400000000001, + 273.096, + 273.55700000000002, + 274.01900000000001, + 274.48099999999999, + 274.94299999999998, + 275.40499999999997, + 275.86700000000002, + 276.32999999999998, + 276.79199999999997, + 277.25400000000002, + 277.71699999999998, + 278.18000000000001, + 278.642, + 279.10500000000002, + 279.56799999999998, + 280.03100000000001, + 280.495, + 280.95800000000003, + 281.42099999999999, + 281.88499999999999, + 282.34800000000001, + 282.81200000000001, + 283.27600000000001, + 283.74000000000001, + 284.20400000000001, + 284.66800000000001, + 285.13200000000001, + 285.596, + 286.06099999999998, + 286.52499999999998, + 286.99000000000001, + 287.45400000000001, + 287.91899999999998, + 288.38299999999998, + 288.84800000000001, + 289.31299999999999, + 289.77699999999999, + 290.24200000000002, + 290.70699999999999, + 291.17200000000003, + 291.63600000000002, + 292.101, + 292.56599999999997, + 293.03100000000001, + 293.49599999999998, + 293.96100000000001, + 294.42599999999999, + 294.89100000000002, + 295.35599999999999, + 295.82100000000003, + 296.286, + 296.75099999999998, + 297.21600000000001, + 297.68099999999998, + 298.14600000000002, + 298.61099999999999, + 299.07600000000002, + 299.54199999999997, + 300.00700000000001, + 300.47199999999998, + 300.93700000000001, + 301.40300000000002, + 301.86799999999999, + 302.33300000000003, + 302.79899999999998, + 303.26400000000001, + 303.73000000000002, + 304.19499999999999, + 304.661, + 305.12599999999998, + 305.59199999999998, + 306.05700000000002, + 306.52300000000002, + 306.988, + 307.45400000000001, + 307.91899999999998, + 308.38499999999999, + 308.851, + 309.31700000000001, + 309.78199999999998, + 310.24799999999999, + 310.714, + 311.18000000000001, + 311.64499999999998, + 312.11099999999999, + 312.577, + 313.04300000000001, + 313.50900000000001, + 313.97500000000002, + 314.44099999999997, + 314.90699999999998, + 315.37299999999999, + 315.839, + 316.30500000000001, + 316.77100000000002, + 317.23700000000002, + 317.70299999999997, + 318.17000000000002, + 318.63600000000002, + 319.10199999999998, + 319.56799999999998, + 320.03500000000003, + 320.50099999999998, + 320.96699999999998, + 321.43400000000003, + 321.89999999999998, + 322.36599999999999, + 322.83300000000003, + 323.29899999999998, + 323.76600000000002, + 324.23200000000003, + 324.69900000000001, + 325.16500000000002, + 325.63200000000001, + 326.09800000000001, + 326.565, + 327.03199999999998, + 327.49799999999999, + 327.96499999999997, + 328.43200000000002, + 328.89800000000002, + 329.36500000000001, + 329.83199999999999, + 330.29899999999998, + 330.76499999999999, + 331.23200000000003, + 331.69900000000001, + 332.166, + 332.63299999999998, + 333.10000000000002, + 333.56700000000001, + 334.03399999999999, + 334.50099999999998, + 334.96800000000002, + 335.435, + 335.90199999999999, + 336.36900000000003, + 336.83600000000001, + 337.30399999999997, + 337.77100000000002, + 338.238, + 338.70499999999998, + 339.17200000000003, + 339.63999999999999, + 340.10700000000003, + 340.57400000000001, + 341.04199999999997, + 341.50900000000001, + 341.97699999999998, + 342.44400000000002, + 342.911, + 343.37900000000002, + 343.846, + 344.31400000000002, + 344.78199999999998, + 345.24900000000002, + 345.71699999999998, + 346.18400000000003, + 346.65199999999999, + 347.12, + 347.58699999999999, + 348.05500000000001, + 348.52300000000002, + 348.99099999999999, + 349.45800000000003, + 349.92599999999999, + 350.39400000000001, + 350.86200000000002, + 351.32999999999998, + 351.798, + 352.26600000000002, + 352.73399999999998, + 353.202, + 353.67000000000002, + 354.13799999999998, + 354.60599999999999, + 355.07400000000001, + 355.54199999999997, + 356.00999999999999, + 356.47800000000001, + 356.947, + 357.41500000000002, + 357.88299999999998, + 358.351, + 358.81999999999999, + 359.28800000000001, + 359.75599999999997, + 360.22500000000002, + 360.69299999999998, + 361.161, + 361.63, + 362.09800000000001, + 362.56700000000001, + 363.03500000000003, + 363.50400000000002, + 363.97199999999998, + 364.44099999999997, + 364.91000000000003, + 365.37799999999999, + 365.84699999999998, + 366.315, + 366.78399999999999, + 367.25299999999999, + 367.72199999999998, + 368.19, + 368.65899999999999, + 369.12799999999999, + 369.59699999999998, + 370.06599999999997, + 370.53500000000003, + 371.00400000000002, + 371.47300000000001, + 371.94200000000001, + 372.411, + 372.88, + 373.34899999999999, + 373.81799999999998, + 374.28699999999998, + 374.75599999999997, + 375.22500000000002, + 375.69400000000002, + 376.16300000000001, + 376.63299999999998, + 377.10199999999998, + 377.57100000000003, + 378.04000000000002, + 378.50999999999999, + 378.97899999999998, + 379.44799999999998, + 379.91800000000001, + 380.387, + 380.85700000000003, + 381.32600000000002, + 381.79599999999999, + 382.26499999999999, + 382.73500000000001, + 383.20400000000001, + 383.67399999999998, + 384.14299999999997, + 384.613, + 394.613, + 404.613, + 414.613, + 424.613, + 434.613, + 444.613, + 454.613, + 464.613, + 474.613, + 484.613, + 494.613, + 504.613, + 514.61300000000006, + 524.61300000000006, + 534.61300000000006, + 544.61300000000006, + 554.61300000000006, + 564.61300000000006, + 574.61300000000006, + 584.61300000000006, + 594.61300000000006, + 604.61300000000006, + 614.61300000000006, + 624.61300000000006, + 634.61300000000006, + 644.61300000000006, + 654.61300000000006, + 664.61300000000006, + 674.61300000000006, + 684.61300000000006, + 694.61300000000006, + 704.61300000000006, + 714.61300000000006, + 724.61300000000006, + 734.61300000000006, + 744.61300000000006, + 754.61300000000006, + 764.61300000000006, + 774.61300000000006, + 784.61300000000006, + 794.61300000000006, + 804.61300000000006, + 814.61300000000006, + 824.61300000000006, + 834.61300000000006, + 844.61300000000006, + 854.61300000000006, + 864.61300000000006, + 874.61300000000006, + 884.61300000000006, + 894.61300000000006, + 904.61300000000006, + 914.61300000000006, + 924.61300000000006, + 934.61300000000006, + 944.61300000000006, + 954.61300000000006, + 964.61300000000006, + 974.61300000000006, + 984.61300000000006, + 994.61300000000006, + 1004.61, + 1014.61, + 1024.6099999999999, + 1034.6099999999999, + 1044.6099999999999, + 1054.6099999999999, + 1064.6099999999999, + 1074.6099999999999, + 1084.6099999999999, + 1094.6099999999999, + 1104.6099999999999 + ], + "values" : + [ + 0.001, + 1.0009999999999999, + 2.0009999999999999, + 3.0009999999999999, + 4.0010000000000003, + 5.0010000000000003, + 6.0010000000000003, + 7.0010000000000003, + 8.0009999999999994, + 9.0009999999999994, + 10.000999999999999, + 11.000999999999999, + 12.000999999999999, + 13.000999999999999, + 14.000999999999999, + 15.000999999999999, + 16.001000000000001, + 17.001000000000001, + 18.001000000000001, + 19.001000000000001, + 20.001000000000001, + 21.001000000000001, + 22.001000000000001, + 23.001000000000001, + 24.001000000000001, + 25.001000000000001, + 26.001000000000001, + 27.001000000000001, + 28.001000000000001, + 29.001000000000001, + 30.001000000000001, + 31.001000000000001, + 32.000999999999998, + 33.000999999999998, + 34.000999999999998, + 35.000999999999998, + 36.000999999999998, + 37.000999999999998, + 38.000999999999998, + 39.000999999999998, + 40.000999999999998, + 41.000999999999998, + 42.000999999999998, + 43.000999999999998, + 44.000999999999998, + 45.000999999999998, + 46.000999999999998, + 47.000999999999998, + 48.000999999999998, + 49.000999999999998, + 50.000999999999998, + 51.000999999999998, + 52.000999999999998, + 53.000999999999998, + 54.000999999999998, + 55.000999999999998, + 56.000999999999998, + 57.000999999999998, + 58.000999999999998, + 59.000999999999998, + 60.000999999999998, + 61.000999999999998, + 62.000999999999998, + 63.000999999999998, + 64.001000000000005, + 65.001000000000005, + 66.001000000000005, + 67.001000000000005, + 68.001000000000005, + 69.001000000000005, + 70.001000000000005, + 71.001000000000005, + 72.001000000000005, + 73.001000000000005, + 74.001000000000005, + 75.001000000000005, + 76.001000000000005, + 77.001000000000005, + 78.001000000000005, + 79.001000000000005, + 80.001000000000005, + 81.001000000000005, + 82.001000000000005, + 83.001000000000005, + 84.001000000000005, + 85.001000000000005, + 86.001000000000005, + 87.001000000000005, + 88.001000000000005, + 89.001000000000005, + 90.001000000000005, + 91.001000000000005, + 92.001000000000005, + 93.001000000000005, + 94.001000000000005, + 95.001000000000005, + 96.001000000000005, + 97.001000000000005, + 98.001000000000005, + 99.001000000000005, + 100.001, + 101.001, + 102.001, + 103.001, + 104.001, + 105.001, + 106.001, + 107.001, + 108.001, + 109.001, + 110.001, + 111.001, + 112.001, + 113.001, + 114.001, + 115.001, + 116.001, + 117.001, + 118.001, + 119.001, + 120.001, + 121.001, + 122.001, + 123.001, + 124.001, + 125.001, + 126.001, + 127.001, + 128.001, + 129.001, + 130.001, + 131.001, + 132.001, + 133.001, + 134.001, + 135.001, + 136.001, + 137.001, + 138.001, + 139.001, + 140.001, + 141.001, + 142.001, + 143.001, + 144.001, + 145.001, + 146.001, + 147.001, + 148.001, + 149.001, + 150.001, + 151.001, + 152.001, + 153.001, + 154.001, + 155.001, + 156.001, + 157.001, + 158.001, + 159.001, + 160.001, + 161.001, + 162.001, + 163.001, + 164.001, + 165.001, + 166.001, + 167.001, + 168.001, + 169.001, + 170.001, + 171.001, + 172.001, + 173.001, + 174.001, + 175.001, + 176.001, + 177.001, + 178.001, + 179.001, + 180.001, + 181.001, + 182.001, + 183.001, + 184.001, + 185.001, + 186.001, + 187.001, + 188.001, + 189.001, + 190.001, + 191.001, + 192.001, + 193.001, + 194.001, + 195.001, + 196.001, + 197.001, + 198.001, + 199.001, + 200.001, + 201.001, + 202.001, + 203.001, + 204.001, + 205.001, + 206.001, + 207.001, + 208.001, + 209.001, + 210.001, + 211.001, + 212.001, + 213.001, + 214.001, + 215.001, + 216.001, + 217.001, + 218.001, + 219.001, + 220.001, + 221.001, + 222.001, + 223.001, + 224.001, + 225.001, + 226.001, + 227.001, + 228.001, + 229.001, + 230.001, + 231.001, + 232.001, + 233.001, + 234.001, + 235.001, + 236.001, + 237.001, + 238.001, + 239.001, + 240.001, + 241.001, + 242.001, + 243.001, + 244.001, + 245.001, + 246.001, + 247.001, + 248.001, + 249.001, + 250.001, + 251.001, + 252.001, + 253.001, + 254.001, + 255.001, + 256.00099999999998, + 257.00099999999998, + 258.00099999999998, + 259.00099999999998, + 260.00099999999998, + 261.00099999999998, + 262.00099999999998, + 263.00099999999998, + 264.00099999999998, + 265.00099999999998, + 266.00099999999998, + 267.00099999999998, + 268.00099999999998, + 269.00099999999998, + 270.00099999999998, + 271.00099999999998, + 272.00099999999998, + 273.00099999999998, + 274.00099999999998, + 275.00099999999998, + 276.00099999999998, + 277.00099999999998, + 278.00099999999998, + 279.00099999999998, + 280.00099999999998, + 281.00099999999998, + 282.00099999999998, + 283.00099999999998, + 284.00099999999998, + 285.00099999999998, + 286.00099999999998, + 287.00099999999998, + 288.00099999999998, + 289.00099999999998, + 290.00099999999998, + 291.00099999999998, + 292.00099999999998, + 293.00099999999998, + 294.00099999999998, + 295.00099999999998, + 296.00099999999998, + 297.00099999999998, + 298.00099999999998, + 299.00099999999998, + 300.00099999999998, + 301.00099999999998, + 302.00099999999998, + 303.00099999999998, + 304.00099999999998, + 305.00099999999998, + 306.00099999999998, + 307.00099999999998, + 308.00099999999998, + 309.00099999999998, + 310.00099999999998, + 311.00099999999998, + 312.00099999999998, + 313.00099999999998, + 314.00099999999998, + 315.00099999999998, + 316.00099999999998, + 317.00099999999998, + 318.00099999999998, + 319.00099999999998, + 320.00099999999998, + 321.00099999999998, + 322.00099999999998, + 323.00099999999998, + 324.00099999999998, + 325.00099999999998, + 326.00099999999998, + 327.00099999999998, + 328.00099999999998, + 329.00099999999998, + 330.00099999999998, + 331.00099999999998, + 332.00099999999998, + 333.00099999999998, + 334.00099999999998, + 335.00099999999998, + 336.00099999999998, + 337.00099999999998, + 338.00099999999998, + 339.00099999999998, + 340.00099999999998, + 341.00099999999998, + 342.00099999999998, + 343.00099999999998, + 344.00099999999998, + 345.00099999999998, + 346.00099999999998, + 347.00099999999998, + 348.00099999999998, + 349.00099999999998, + 350.00099999999998, + 351.00099999999998, + 352.00099999999998, + 353.00099999999998, + 354.00099999999998, + 355.00099999999998, + 356.00099999999998, + 357.00099999999998, + 358.00099999999998, + 359.00099999999998, + 360.00099999999998, + 361.00099999999998, + 362.00099999999998, + 363.00099999999998, + 364.00099999999998, + 365.00099999999998, + 366.00099999999998, + 367.00099999999998, + 368.00099999999998, + 369.00099999999998, + 370.00099999999998, + 371.00099999999998, + 372.00099999999998, + 373.00099999999998, + 374.00099999999998, + 375.00099999999998, + 376.00099999999998, + 377.00099999999998, + 378.00099999999998, + 379.00099999999998, + 380.00099999999998, + 381.00099999999998, + 382.00099999999998, + 383.00099999999998, + 384.00099999999998, + 385.00099999999998, + 386.00099999999998, + 387.00099999999998, + 388.00099999999998, + 389.00099999999998, + 390.00099999999998, + 391.00099999999998, + 392.00099999999998, + 393.00099999999998, + 394.00099999999998, + 395.00099999999998, + 396.00099999999998, + 397.00099999999998, + 398.00099999999998, + 399.00099999999998, + 400.00099999999998, + 401.00099999999998, + 402.00099999999998, + 403.00099999999998, + 404.00099999999998, + 405.00099999999998, + 406.00099999999998, + 407.00099999999998, + 408.00099999999998, + 409.00099999999998, + 410.00099999999998, + 411.00099999999998, + 412.00099999999998, + 413.00099999999998, + 414.00099999999998, + 415.00099999999998, + 416.00099999999998, + 417.00099999999998, + 418.00099999999998, + 419.00099999999998, + 420.00099999999998, + 421.00099999999998, + 422.00099999999998, + 423.00099999999998, + 424.00099999999998, + 425.00099999999998, + 426.00099999999998, + 427.00099999999998, + 428.00099999999998, + 429.00099999999998, + 430.00099999999998, + 431.00099999999998, + 432.00099999999998, + 433.00099999999998, + 434.00099999999998, + 435.00099999999998, + 436.00099999999998, + 437.00099999999998, + 438.00099999999998, + 439.00099999999998, + 440.00099999999998, + 441.00099999999998, + 442.00099999999998, + 443.00099999999998, + 444.00099999999998, + 445.00099999999998, + 446.00099999999998, + 447.00099999999998, + 448.00099999999998, + 449.00099999999998, + 450.00099999999998, + 451.00099999999998, + 452.00099999999998, + 453.00099999999998, + 454.00099999999998, + 455.00099999999998, + 456.00099999999998, + 457.00099999999998, + 458.00099999999998, + 459.00099999999998, + 460.00099999999998, + 461.00099999999998, + 462.00099999999998, + 463.00099999999998, + 464.00099999999998, + 465.00099999999998, + 466.00099999999998, + 467.00099999999998, + 468.00099999999998, + 469.00099999999998, + 470.00099999999998, + 471.00099999999998, + 472.00099999999998, + 473.00099999999998, + 474.00099999999998, + 475.00099999999998, + 476.00099999999998, + 477.00099999999998, + 478.00099999999998, + 479.00099999999998, + 480.00099999999998, + 481.00099999999998, + 482.00099999999998, + 483.00099999999998, + 484.00099999999998, + 485.00099999999998, + 486.00099999999998, + 487.00099999999998, + 488.00099999999998, + 489.00099999999998, + 490.00099999999998, + 491.00099999999998, + 492.00099999999998, + 493.00099999999998, + 494.00099999999998, + 495.00099999999998, + 496.00099999999998, + 497.00099999999998, + 498.00099999999998, + 499.00099999999998, + 500.00099999999998, + 501.00099999999998, + 502.00099999999998, + 503.00099999999998, + 504.00099999999998, + 505.00099999999998, + 506.00099999999998, + 507.00099999999998, + 508.00099999999998, + 509.00099999999998, + 510.00099999999998, + 511.00099999999998, + 512.00099999999998, + 513.00099999999998, + 514.00099999999998, + 515.00099999999998, + 516.00099999999998, + 517.00099999999998, + 518.00099999999998, + 519.00099999999998, + 520.00099999999998, + 521.00099999999998, + 522.00099999999998, + 523.00099999999998, + 524.00099999999998, + 525.00099999999998, + 526.00099999999998, + 527.00099999999998, + 528.00099999999998, + 529.00099999999998, + 530.00099999999998, + 531.00099999999998, + 532.00099999999998, + 533.00099999999998, + 534.00099999999998, + 535.00099999999998, + 536.00099999999998, + 537.00099999999998, + 538.00099999999998, + 539.00099999999998, + 540.00099999999998, + 541.00099999999998, + 542.00099999999998, + 543.00099999999998, + 544.00099999999998, + 545.00099999999998, + 546.00099999999998, + 547.00099999999998, + 548.00099999999998, + 549.00099999999998, + 550.00099999999998, + 551.00099999999998, + 552.00099999999998, + 553.00099999999998, + 554.00099999999998, + 555.00099999999998, + 556.00099999999998, + 557.00099999999998, + 558.00099999999998, + 559.00099999999998, + 560.00099999999998, + 561.00099999999998, + 562.00099999999998, + 563.00099999999998, + 564.00099999999998, + 565.00099999999998, + 566.00099999999998, + 567.00099999999998, + 568.00099999999998, + 569.00099999999998, + 570.00099999999998, + 571.00099999999998, + 572.00099999999998, + 573.00099999999998, + 574.00099999999998, + 575.00099999999998, + 576.00099999999998, + 577.00099999999998, + 578.00099999999998, + 579.00099999999998, + 580.00099999999998, + 581.00099999999998, + 582.00099999999998, + 583.00099999999998, + 584.00099999999998, + 585.00099999999998, + 586.00099999999998, + 587.00099999999998, + 588.00099999999998, + 589.00099999999998, + 590.00099999999998, + 591.00099999999998, + 592.00099999999998, + 593.00099999999998, + 594.00099999999998, + 595.00099999999998, + 596.00099999999998, + 597.00099999999998, + 598.00099999999998, + 599.00099999999998, + 600.00099999999998, + 601.00099999999998, + 602.00099999999998, + 603.00099999999998, + 604.00099999999998, + 605.00099999999998, + 606.00099999999998, + 607.00099999999998, + 608.00099999999998, + 609.00099999999998, + 610.00099999999998, + 611.00099999999998, + 612.00099999999998, + 613.00099999999998, + 614.00099999999998, + 615.00099999999998, + 616.00099999999998, + 617.00099999999998, + 618.00099999999998, + 619.00099999999998, + 620.00099999999998, + 621.00099999999998, + 622.00099999999998, + 623.00099999999998, + 624.00099999999998, + 625.00099999999998, + 626.00099999999998, + 627.00099999999998, + 628.00099999999998, + 629.00099999999998, + 630.00099999999998, + 631.00099999999998, + 632.00099999999998, + 633.00099999999998, + 634.00099999999998, + 635.00099999999998, + 636.00099999999998, + 637.00099999999998, + 638.00099999999998, + 639.00099999999998, + 640.00099999999998, + 641.00099999999998, + 642.00099999999998, + 643.00099999999998, + 644.00099999999998, + 645.00099999999998, + 646.00099999999998, + 647.00099999999998, + 648.00099999999998, + 649.00099999999998, + 650.00099999999998, + 651.00099999999998, + 652.00099999999998, + 653.00099999999998, + 654.00099999999998, + 655.00099999999998, + 656.00099999999998, + 657.00099999999998, + 658.00099999999998, + 659.00099999999998, + 660.00099999999998, + 661.00099999999998, + 662.00099999999998, + 663.00099999999998, + 664.00099999999998, + 665.00099999999998, + 666.00099999999998, + 667.00099999999998, + 668.00099999999998, + 669.00099999999998, + 670.00099999999998, + 671.00099999999998, + 672.00099999999998, + 673.00099999999998, + 674.00099999999998, + 675.00099999999998, + 676.00099999999998, + 677.00099999999998, + 678.00099999999998, + 679.00099999999998, + 680.00099999999998, + 681.00099999999998, + 682.00099999999998, + 683.00099999999998, + 684.00099999999998, + 685.00099999999998, + 686.00099999999998, + 687.00099999999998, + 688.00099999999998, + 689.00099999999998, + 690.00099999999998, + 691.00099999999998, + 692.00099999999998, + 693.00099999999998, + 694.00099999999998, + 695.00099999999998, + 696.00099999999998, + 697.00099999999998, + 698.00099999999998, + 699.00099999999998, + 700.00099999999998, + 701.00099999999998, + 702.00099999999998, + 703.00099999999998, + 704.00099999999998, + 705.00099999999998, + 706.00099999999998, + 707.00099999999998, + 708.00099999999998, + 709.00099999999998, + 710.00099999999998, + 711.00099999999998, + 712.00099999999998, + 713.00099999999998, + 714.00099999999998, + 715.00099999999998, + 716.00099999999998, + 717.00099999999998, + 718.00099999999998, + 719.00099999999998, + 720.00099999999998, + 721.00099999999998, + 722.00099999999998, + 723.00099999999998, + 724.00099999999998, + 725.00099999999998, + 726.00099999999998, + 727.00099999999998, + 728.00099999999998, + 729.00099999999998, + 730.00099999999998, + 731.00099999999998, + 732.00099999999998, + 733.00099999999998, + 734.00099999999998, + 735.00099999999998, + 736.00099999999998, + 737.00099999999998, + 738.00099999999998, + 739.00099999999998, + 740.00099999999998, + 741.00099999999998, + 742.00099999999998, + 743.00099999999998, + 744.00099999999998, + 745.00099999999998, + 746.00099999999998, + 747.00099999999998, + 748.00099999999998, + 749.00099999999998, + 750.00099999999998, + 751.00099999999998, + 752.00099999999998, + 753.00099999999998, + 754.00099999999998, + 755.00099999999998, + 756.00099999999998, + 757.00099999999998, + 758.00099999999998, + 759.00099999999998, + 760.00099999999998, + 761.00099999999998, + 762.00099999999998, + 763.00099999999998, + 764.00099999999998, + 765.00099999999998, + 766.00099999999998, + 767.00099999999998, + 768.00099999999998, + 769.00099999999998, + 770.00099999999998, + 771.00099999999998, + 772.00099999999998, + 773.00099999999998, + 774.00099999999998, + 775.00099999999998, + 776.00099999999998, + 777.00099999999998, + 778.00099999999998, + 779.00099999999998, + 780.00099999999998, + 781.00099999999998, + 782.00099999999998, + 783.00099999999998, + 784.00099999999998, + 785.00099999999998, + 786.00099999999998, + 787.00099999999998, + 788.00099999999998, + 789.00099999999998, + 790.00099999999998, + 791.00099999999998, + 792.00099999999998, + 793.00099999999998, + 794.00099999999998, + 795.00099999999998, + 796.00099999999998, + 797.00099999999998, + 798.00099999999998, + 799.00099999999998, + 800.00099999999998, + 801.00099999999998, + 802.00099999999998, + 803.00099999999998, + 804.00099999999998, + 805.00099999999998, + 806.00099999999998, + 807.00099999999998, + 808.00099999999998, + 809.00099999999998, + 810.00099999999998, + 811.00099999999998, + 812.00099999999998, + 813.00099999999998, + 814.00099999999998, + 815.00099999999998, + 816.00099999999998, + 817.00099999999998, + 818.00099999999998, + 819.00099999999998, + 820.00099999999998, + 821.00099999999998, + 822.00099999999998, + 823.00099999999998, + 824.00099999999998, + 825.00099999999998, + 826.00099999999998, + 827.00099999999998, + 828.00099999999998, + 829.00099999999998, + 830.00099999999998, + 831.00099999999998, + 832.00099999999998, + 833.00099999999998, + 834.00099999999998, + 835.00099999999998, + 836.00099999999998, + 837.00099999999998, + 838.00099999999998, + 839.00099999999998, + 840.00099999999998, + 841.00099999999998, + 842.00099999999998, + 843.00099999999998, + 844.00099999999998, + 845.00099999999998, + 846.00099999999998, + 847.00099999999998, + 848.00099999999998, + 849.00099999999998, + 850.00099999999998, + 851.00099999999998, + 852.00099999999998, + 853.00099999999998, + 854.00099999999998, + 855.00099999999998, + 856.00099999999998, + 857.00099999999998, + 858.00099999999998, + 859.00099999999998, + 860.00099999999998, + 861.00099999999998, + 862.00099999999998, + 863.00099999999998, + 864.00099999999998, + 865.00099999999998, + 866.00099999999998, + 867.00099999999998, + 868.00099999999998, + 869.00099999999998, + 870.00099999999998, + 871.00099999999998, + 872.00099999999998, + 873.00099999999998, + 874.00099999999998, + 875.00099999999998, + 876.00099999999998, + 877.00099999999998, + 878.00099999999998, + 879.00099999999998, + 880.00099999999998, + 881.00099999999998, + 882.00099999999998, + 883.00099999999998, + 884.00099999999998, + 885.00099999999998, + 886.00099999999998, + 887.00099999999998, + 888.00099999999998, + 889.00099999999998, + 890.00099999999998, + 891.00099999999998, + 892.00099999999998, + 893.00099999999998, + 894.00099999999998, + 895.00099999999998, + 896.00099999999998, + 897.00099999999998, + 898.00099999999998, + 899.00099999999998, + 900.00099999999998, + 901.00099999999998, + 902.00099999999998, + 903.00099999999998, + 904.00099999999998, + 905.00099999999998, + 906.00099999999998, + 907.00099999999998, + 908.00099999999998, + 909.00099999999998, + 910.00099999999998, + 911.00099999999998, + 912.00099999999998, + 913.00099999999998, + 914.00099999999998, + 915.00099999999998, + 916.00099999999998, + 917.00099999999998, + 918.00099999999998, + 919.00099999999998, + 920.00099999999998, + 921.00099999999998, + 922.00099999999998, + 923.00099999999998, + 924.00099999999998, + 925.00099999999998, + 926.00099999999998, + 927.00099999999998, + 928.00099999999998, + 929.00099999999998, + 930.00099999999998, + 931.00099999999998, + 932.00099999999998, + 933.00099999999998, + 934.00099999999998, + 935.00099999999998, + 936.00099999999998, + 937.00099999999998, + 938.00099999999998, + 939.00099999999998, + 940.00099999999998, + 941.00099999999998, + 942.00099999999998, + 943.00099999999998, + 944.00099999999998, + 945.00099999999998, + 946.00099999999998, + 947.00099999999998, + 948.00099999999998, + 949.00099999999998, + 950.00099999999998, + 951.00099999999998, + 952.00099999999998, + 953.00099999999998, + 954.00099999999998, + 955.00099999999998, + 956.00099999999998, + 957.00099999999998, + 958.00099999999998, + 959.00099999999998, + 960.00099999999998, + 961.00099999999998, + 962.00099999999998, + 963.00099999999998, + 964.00099999999998, + 965.00099999999998, + 966.00099999999998, + 967.00099999999998, + 968.00099999999998, + 969.00099999999998, + 970.00099999999998, + 971.00099999999998, + 972.00099999999998, + 973.00099999999998, + 974.00099999999998, + 975.00099999999998, + 976.00099999999998, + 977.00099999999998, + 978.00099999999998, + 979.00099999999998, + 980.00099999999998, + 981.00099999999998, + 982.00099999999998, + 983.00099999999998, + 984.00099999999998, + 985.00099999999998, + 986.00099999999998, + 987.00099999999998, + 988.00099999999998, + 989.00099999999998, + 990.00099999999998, + 991.00099999999998, + 992.00099999999998, + 993.00099999999998, + 994.00099999999998, + 995.00099999999998, + 996.00099999999998, + 997.00099999999998, + 998.00099999999998, + 999.00099999999998, + 1020, + 1041, + 1062, + 1083, + 1104, + 1125, + 1146, + 1167, + 1188, + 1209, + 1230, + 1251, + 1272, + 1293, + 1314, + 1335, + 1356, + 1377, + 1398, + 1419, + 1440, + 1461, + 1482, + 1503, + 1524, + 1545, + 1566, + 1587, + 1608, + 1629, + 1650, + 1671, + 1692, + 1713, + 1734, + 1755, + 1776, + 1797, + 1818, + 1839, + 1860, + 1881, + 1902, + 1923, + 1944, + 1965, + 1986, + 2007, + 2028, + 2049, + 2070, + 2091, + 2112, + 2133, + 2154, + 2175, + 2196, + 2217, + 2238, + 2259, + 2280, + 2301, + 2322, + 2343, + 2364, + 2385, + 2406, + 2427, + 2448, + 2469, + 2490, + 2511 + ] + }, + "name" : "KaonRange", + "type" : "LinterpFunction" + }, + { + "data" : + { + "coords" : + [ + 7.6572100000000008e-06, + 0.0031029999999999999, + 0.0091284000000000001, + 0.0176375, + 0.028391199999999998, + 0.041280699999999997, + 0.056212699999999997, + 0.073113800000000007, + 0.091926300000000002, + 0.11260299999999999, + 0.135101, + 0.159272, + 0.185117, + 0.212807, + 0.24210999999999999, + 0.27313500000000002, + 0.30583100000000002, + 0.340111, + 0.376083, + 0.41358299999999998, + 0.45268900000000001, + 0.49329299999999998, + 0.53525900000000004, + 0.57867999999999997, + 0.62365999999999999, + 0.67031600000000002, + 0.71854700000000005, + 0.76822199999999996, + 0.81940199999999996, + 0.87199300000000002, + 0.92604600000000004, + 0.98148500000000005, + 1.0382199999999999, + 1.09633, + 1.1558600000000001, + 1.21689, + 1.27932, + 1.3430299999999999, + 1.4080600000000001, + 1.4744900000000001, + 1.54236, + 1.61158, + 1.68204, + 1.75379, + 1.82687, + 1.90134, + 1.9771099999999999, + 2.05409, + 2.1323300000000001, + 2.2118699999999998, + 2.2927499999999998, + 2.3748900000000002, + 2.4582199999999998, + 2.54277, + 2.6285799999999999, + 2.7156899999999999, + 2.80402, + 2.8935200000000001, + 2.98421, + 3.07612, + 3.1692900000000002, + 3.2636699999999998, + 3.3591700000000002, + 3.4558499999999999, + 3.5537200000000002, + 3.6528100000000001, + 3.7530700000000001, + 3.8544399999999999, + 3.95696, + 4.0606299999999997, + 4.1654999999999998, + 4.2714999999999996, + 4.37859, + 4.4867900000000001, + 4.5961299999999996, + 4.70662, + 4.8182299999999998, + 4.9309000000000003, + 5.0446600000000004, + 5.1595300000000002, + 5.2755299999999998, + 5.39262, + 5.5107600000000003, + 5.6299599999999996, + 5.7502500000000003, + 5.8716400000000002, + 5.9941000000000004, + 6.1175800000000002, + 6.2421100000000003, + 6.3677000000000001, + 6.49437, + 6.6220800000000004, + 6.7507900000000003, + 6.8805300000000003, + 7.0113000000000003, + 7.1431300000000002, + 7.2759799999999997, + 7.4098199999999999, + 7.5446600000000004, + 7.6805199999999996, + 7.8174200000000003, + 7.9552399999999999, + 8.0938800000000004, + 8.2333400000000001, + 8.3736499999999996, + 8.5148100000000007, + 8.6568299999999994, + 8.7997200000000007, + 8.9434799999999992, + 9.0881399999999992, + 9.2337100000000003, + 9.3801900000000007, + 9.52759, + 9.6759299999999993, + 9.8252199999999998, + 9.9754799999999992, + 10.1267, + 10.2789, + 10.4321, + 10.586399999999999, + 10.7416, + 10.8979, + 11.055300000000001, + 11.213699999999999, + 11.373200000000001, + 11.533799999999999, + 11.6953, + 11.8576, + 12.0207, + 12.1846, + 12.349299999999999, + 12.514699999999999, + 12.680999999999999, + 12.848100000000001, + 13.016, + 13.184699999999999, + 13.3543, + 13.524800000000001, + 13.696, + 13.8682, + 14.0412, + 14.215199999999999, + 14.390000000000001, + 14.5657, + 14.7423, + 14.9198, + 15.0983, + 15.277699999999999, + 15.4581, + 15.6394, + 15.8217, + 16.004899999999999, + 16.188700000000001, + 16.3733, + 16.558599999999998, + 16.744700000000002, + 16.9315, + 17.119, + 17.307300000000001, + 17.496300000000002, + 17.686199999999999, + 17.8767, + 18.068100000000001, + 18.260300000000001, + 18.453199999999999, + 18.646899999999999, + 18.8415, + 19.036799999999999, + 19.233000000000001, + 19.43, + 19.627800000000001, + 19.826499999999999, + 20.026, + 20.226400000000002, + 20.427600000000002, + 20.6297, + 20.8325, + 21.036100000000001, + 21.240300000000001, + 21.4452, + 21.650700000000001, + 21.856999999999999, + 22.064, + 22.271599999999999, + 22.48, + 22.689, + 22.898800000000001, + 23.109300000000001, + 23.320499999999999, + 23.532499999999999, + 23.745100000000001, + 23.958500000000001, + 24.172699999999999, + 24.387599999999999, + 24.603300000000001, + 24.819700000000001, + 25.036799999999999, + 25.254799999999999, + 25.473500000000001, + 25.693000000000001, + 25.9133, + 26.1343, + 26.355899999999998, + 26.578199999999999, + 26.800999999999998, + 27.0245, + 27.248699999999999, + 27.473500000000001, + 27.698899999999998, + 27.925000000000001, + 28.151700000000002, + 28.379100000000001, + 28.607199999999999, + 28.835899999999999, + 29.065300000000001, + 29.295300000000001, + 29.5261, + 29.7575, + 29.989599999999999, + 30.222300000000001, + 30.4558, + 30.690000000000001, + 30.924900000000001, + 31.160399999999999, + 31.396699999999999, + 31.633700000000001, + 31.871400000000001, + 32.1096, + 32.348399999999998, + 32.587800000000001, + 32.827800000000003, + 33.068399999999997, + 33.3095, + 33.551299999999998, + 33.793700000000001, + 34.036700000000003, + 34.280299999999997, + 34.524500000000003, + 34.769300000000001, + 35.014699999999998, + 35.260800000000003, + 35.5075, + 35.754800000000003, + 36.002699999999997, + 36.251300000000001, + 36.500500000000002, + 36.750300000000003, + 37.000799999999998, + 37.251899999999999, + 37.503700000000002, + 37.7562, + 38.0092, + 38.262799999999999, + 38.5169, + 38.771599999999999, + 39.026800000000001, + 39.282499999999999, + 39.538800000000002, + 39.795699999999997, + 40.053100000000001, + 40.311100000000003, + 40.569699999999997, + 40.828800000000001, + 41.088500000000003, + 41.348700000000001, + 41.6096, + 41.871000000000002, + 42.132899999999999, + 42.395499999999998, + 42.6586, + 42.922400000000003, + 43.186700000000002, + 43.451599999999999, + 43.717100000000002, + 43.983199999999997, + 44.2498, + 44.517099999999999, + 44.784799999999997, + 45.053100000000001, + 45.321800000000003, + 45.591099999999997, + 45.860900000000001, + 46.1312, + 46.402000000000001, + 46.673299999999998, + 46.945099999999996, + 47.217500000000001, + 47.490400000000001, + 47.763800000000003, + 48.037700000000001, + 48.312199999999997, + 48.587200000000003, + 48.862699999999997, + 49.138800000000003, + 49.415399999999998, + 49.692500000000003, + 49.970199999999998, + 50.2485, + 50.527200000000001, + 50.8065, + 51.086399999999998, + 51.366799999999998, + 51.647599999999997, + 51.928800000000003, + 52.210500000000003, + 52.492600000000003, + 52.775199999999998, + 53.058199999999999, + 53.341700000000003, + 53.625599999999999, + 53.9099, + 54.194699999999997, + 54.479999999999997, + 54.765700000000002, + 55.0518, + 55.3384, + 55.625500000000002, + 55.912999999999997, + 56.201000000000001, + 56.4895, + 56.778399999999998, + 57.067799999999998, + 57.357599999999998, + 57.6479, + 57.938699999999997, + 58.229999999999997, + 58.521700000000003, + 58.813899999999997, + 59.1066, + 59.399700000000003, + 59.693399999999997, + 59.987499999999997, + 60.2821, + 60.577100000000002, + 60.872700000000002, + 61.168700000000001, + 61.465299999999999, + 61.762300000000003, + 62.059800000000003, + 62.357799999999997, + 62.656300000000002, + 62.955300000000001, + 63.254800000000003, + 63.5548, + 63.8553, + 64.156300000000002, + 64.457800000000006, + 64.759799999999998, + 65.062299999999993, + 65.365300000000005, + 65.668800000000005, + 65.972800000000007, + 66.277199999999993, + 66.581900000000005, + 66.887100000000004, + 67.192599999999999, + 67.498500000000007, + 67.8048, + 68.111400000000003, + 68.418499999999995, + 68.725999999999999, + 69.033799999999999, + 69.341999999999999, + 69.650700000000001, + 69.959699999999998, + 70.269099999999995, + 70.578900000000004, + 70.889099999999999, + 71.199799999999996, + 71.510800000000003, + 71.822199999999995, + 72.134, + 72.446200000000005, + 72.758799999999994, + 73.071899999999999, + 73.385300000000001, + 73.699100000000001, + 74.013400000000004, + 74.328000000000003, + 74.643100000000004, + 74.958500000000001, + 75.2744, + 75.590699999999998, + 75.907399999999996, + 76.224599999999995, + 76.542100000000005, + 76.860100000000003, + 77.178399999999996, + 77.497200000000007, + 77.816400000000002, + 78.136099999999999, + 78.456100000000006, + 78.776600000000002, + 79.097499999999997, + 79.418899999999994, + 79.740600000000001, + 80.062799999999996, + 80.385400000000004, + 80.708500000000001, + 81.031999999999996, + 81.355900000000005, + 81.680199999999999, + 82.004800000000003, + 82.329800000000006, + 82.655100000000004, + 82.980800000000002, + 83.306799999999996, + 83.633099999999999, + 83.959800000000001, + 84.286799999999999, + 84.614199999999997, + 84.941900000000004, + 85.269999999999996, + 85.598399999999998, + 85.927099999999996, + 86.256200000000007, + 86.585700000000003, + 86.915499999999994, + 87.245599999999996, + 87.576099999999997, + 87.906999999999996, + 88.238200000000006, + 88.569699999999997, + 88.901600000000002, + 89.233900000000006, + 89.566500000000005, + 89.899500000000003, + 90.232799999999997, + 90.566500000000005, + 90.900499999999994, + 91.234899999999996, + 91.569699999999997, + 91.904799999999994, + 92.240300000000005, + 92.576099999999997, + 92.912300000000002, + 93.248900000000006, + 93.585800000000006, + 93.923100000000005, + 94.260800000000003, + 94.598799999999997, + 94.937200000000004, + 95.275999999999996, + 95.615099999999998, + 95.954599999999999, + 96.294499999999999, + 96.634699999999995, + 96.975300000000004, + 97.316299999999998, + 97.657700000000006, + 97.999399999999994, + 98.341499999999996, + 98.683899999999994, + 99.026499999999999, + 99.369500000000002, + 99.712800000000001, + 100.056, + 100.40000000000001, + 100.744, + 101.089, + 101.434, + 101.779, + 102.124, + 102.47, + 102.816, + 103.16200000000001, + 103.509, + 103.85599999999999, + 104.203, + 104.55, + 104.898, + 105.246, + 105.595, + 105.943, + 106.292, + 106.642, + 106.991, + 107.34099999999999, + 107.69199999999999, + 108.042, + 108.393, + 108.744, + 109.096, + 109.44799999999999, + 109.8, + 110.15300000000001, + 110.505, + 110.858, + 111.212, + 111.566, + 111.92, + 112.274, + 112.629, + 112.98399999999999, + 113.339, + 113.69499999999999, + 114.051, + 114.407, + 114.764, + 115.121, + 115.47799999999999, + 115.836, + 116.194, + 116.55200000000001, + 116.91, + 117.26900000000001, + 117.628, + 117.98699999999999, + 118.346, + 118.706, + 119.066, + 119.426, + 119.786, + 120.14700000000001, + 120.508, + 120.869, + 121.23099999999999, + 121.593, + 121.955, + 122.31699999999999, + 122.68000000000001, + 123.04300000000001, + 123.40600000000001, + 123.76900000000001, + 124.133, + 124.497, + 124.861, + 125.22499999999999, + 125.59, + 125.955, + 126.321, + 126.68600000000001, + 127.05200000000001, + 127.41800000000001, + 127.785, + 128.15100000000001, + 128.518, + 128.88499999999999, + 129.25299999999999, + 129.62100000000001, + 129.989, + 130.357, + 130.726, + 131.09399999999999, + 131.464, + 131.833, + 132.203, + 132.57300000000001, + 132.94300000000001, + 133.31399999999999, + 133.684, + 134.05500000000001, + 134.42699999999999, + 134.798, + 135.16999999999999, + 135.542, + 135.91399999999999, + 136.28700000000001, + 136.65899999999999, + 137.03200000000001, + 137.40600000000001, + 137.779, + 138.15299999999999, + 138.52600000000001, + 138.90100000000001, + 139.27500000000001, + 139.649, + 140.024, + 140.399, + 140.77500000000001, + 141.15000000000001, + 141.52600000000001, + 141.90199999999999, + 142.27799999999999, + 142.655, + 143.03100000000001, + 143.40799999999999, + 143.785, + 144.16300000000001, + 144.53999999999999, + 144.91800000000001, + 145.29599999999999, + 145.67500000000001, + 146.053, + 146.43199999999999, + 146.81100000000001, + 147.191, + 147.56999999999999, + 147.94999999999999, + 148.33000000000001, + 148.71000000000001, + 149.09100000000001, + 149.47200000000001, + 149.85300000000001, + 150.23400000000001, + 150.61600000000001, + 150.99700000000001, + 151.37899999999999, + 151.762, + 152.14400000000001, + 152.52699999999999, + 152.91, + 153.29300000000001, + 153.67599999999999, + 154.06, + 154.44399999999999, + 154.828, + 155.21199999999999, + 155.596, + 155.98099999999999, + 156.36600000000001, + 156.751, + 157.136, + 157.52099999999999, + 157.90700000000001, + 158.29300000000001, + 158.679, + 159.065, + 159.452, + 159.83799999999999, + 160.22499999999999, + 160.61199999999999, + 161, + 161.387, + 161.77500000000001, + 162.16300000000001, + 162.55099999999999, + 162.93899999999999, + 163.328, + 163.71700000000001, + 164.10599999999999, + 164.495, + 164.88399999999999, + 165.274, + 165.66399999999999, + 166.054, + 166.44399999999999, + 166.83500000000001, + 167.22499999999999, + 167.61600000000001, + 168.00700000000001, + 168.399, + 168.78999999999999, + 169.18199999999999, + 169.57400000000001, + 169.96600000000001, + 170.35900000000001, + 170.751, + 171.14400000000001, + 171.53700000000001, + 171.93000000000001, + 172.32400000000001, + 172.71700000000001, + 173.11099999999999, + 173.505, + 173.899, + 174.29400000000001, + 174.68799999999999, + 175.083, + 175.47800000000001, + 175.87299999999999, + 176.268, + 176.66399999999999, + 177.06, + 177.45500000000001, + 177.851, + 178.24799999999999, + 178.64400000000001, + 179.041, + 179.43700000000001, + 179.834, + 180.23099999999999, + 180.62899999999999, + 181.02600000000001, + 181.42400000000001, + 181.822, + 182.22, + 182.61799999999999, + 183.017, + 183.41499999999999, + 183.81399999999999, + 184.21299999999999, + 184.61199999999999, + 185.011, + 185.411, + 185.81100000000001, + 186.21100000000001, + 186.61099999999999, + 187.011, + 187.41200000000001, + 187.81200000000001, + 188.21299999999999, + 188.614, + 189.01499999999999, + 189.417, + 189.81800000000001, + 190.22, + 190.62200000000001, + 191.024, + 191.42699999999999, + 191.82900000000001, + 192.232, + 192.63499999999999, + 193.03800000000001, + 193.441, + 193.845, + 194.24799999999999, + 194.65199999999999, + 195.05600000000001, + 195.46000000000001, + 195.864, + 196.268, + 196.673, + 197.077, + 197.482, + 197.887, + 198.292, + 198.69800000000001, + 199.10300000000001, + 199.50899999999999, + 199.91399999999999, + 200.31999999999999, + 200.726, + 201.13300000000001, + 201.53899999999999, + 201.946, + 202.352, + 202.75899999999999, + 203.166, + 203.57400000000001, + 203.98099999999999, + 204.38900000000001, + 204.79599999999999, + 205.20400000000001, + 205.61199999999999, + 206.02000000000001, + 206.429, + 206.83699999999999, + 207.24600000000001, + 207.655, + 208.06399999999999, + 208.47300000000001, + 208.88200000000001, + 209.292, + 209.702, + 210.11099999999999, + 210.52099999999999, + 210.93199999999999, + 211.34200000000001, + 211.75200000000001, + 212.16300000000001, + 212.57400000000001, + 212.98500000000001, + 213.39599999999999, + 213.80699999999999, + 214.21899999999999, + 214.63, + 215.042, + 215.45400000000001, + 215.86500000000001, + 216.27799999999999, + 216.69, + 217.102, + 217.51499999999999, + 217.92699999999999, + 218.34, + 218.75299999999999, + 219.166, + 219.57900000000001, + 219.99299999999999, + 220.40600000000001, + 220.81999999999999, + 221.23400000000001, + 221.648, + 222.06200000000001, + 222.476, + 222.88999999999999, + 223.30500000000001, + 223.72, + 224.13399999999999, + 224.54900000000001, + 224.964, + 225.38, + 225.79499999999999, + 226.21100000000001, + 226.626, + 227.042, + 227.458, + 227.874, + 228.28999999999999, + 228.70699999999999, + 229.12299999999999, + 229.53999999999999, + 229.95699999999999, + 230.374, + 230.791, + 231.208, + 231.626, + 232.04300000000001, + 232.46100000000001, + 232.87899999999999, + 233.297, + 233.715, + 234.13300000000001, + 234.55199999999999, + 234.97, + 235.38900000000001, + 235.80699999999999, + 236.226, + 236.64500000000001, + 237.06399999999999, + 237.48400000000001, + 237.90299999999999, + 238.32300000000001, + 238.74199999999999, + 239.16200000000001, + 239.58199999999999, + 240.00200000000001, + 240.422, + 240.84200000000001, + 241.26300000000001, + 241.68299999999999, + 242.10400000000001, + 242.52500000000001, + 242.946, + 243.36699999999999, + 243.78800000000001, + 244.209, + 244.631, + 245.05199999999999, + 245.47399999999999, + 245.89599999999999, + 246.31800000000001, + 246.74000000000001, + 247.16200000000001, + 247.58500000000001, + 248.00700000000001, + 248.43000000000001, + 248.852, + 249.27500000000001, + 249.69800000000001, + 250.12100000000001, + 250.54499999999999, + 250.96799999999999, + 251.392, + 251.815, + 252.239, + 252.66300000000001, + 253.08699999999999, + 253.511, + 253.935, + 254.36000000000001, + 254.78399999999999, + 255.209, + 255.63399999999999, + 256.05900000000003, + 256.48399999999998, + 256.90899999999999, + 257.334, + 257.75900000000001, + 258.185, + 258.61099999999999, + 259.036, + 259.46199999999999, + 259.88799999999998, + 260.31400000000002, + 260.74000000000001, + 261.166, + 261.59300000000002, + 262.01900000000001, + 262.44600000000003, + 262.87299999999999, + 263.30000000000001, + 263.72699999999998, + 264.154, + 264.58100000000002, + 265.00799999999998, + 265.43599999999998, + 265.863, + 266.291, + 266.71899999999999, + 267.14699999999999, + 267.57499999999999, + 268.00299999999999, + 268.43099999999998, + 268.86000000000001, + 269.28800000000001, + 269.71699999999998, + 270.14600000000002, + 270.57400000000001, + 271.00299999999999, + 271.43200000000002, + 271.86200000000002, + 272.291, + 272.72000000000003, + 273.14999999999998, + 273.57999999999998, + 274.00999999999999, + 274.44, + 274.87, + 275.30000000000001, + 275.73000000000002, + 276.16000000000003, + 276.59100000000001, + 277.02100000000002, + 277.452, + 277.88299999999998, + 278.31400000000002, + 278.745, + 279.17599999999999, + 279.60700000000003, + 280.03800000000001, + 280.47000000000003, + 280.90100000000001, + 281.33300000000003, + 281.76400000000001, + 282.19600000000003, + 282.62799999999999, + 283.06, + 283.49200000000002, + 283.92399999999998, + 284.35599999999999, + 284.78899999999999, + 285.221, + 285.654, + 286.08600000000001, + 286.51900000000001, + 286.952, + 287.38499999999999, + 287.81799999999998, + 288.25099999999998, + 288.68400000000003, + 289.11799999999999, + 289.55099999999999, + 289.98500000000001, + 290.41800000000001, + 290.85199999999998, + 291.286, + 291.72000000000003, + 292.154, + 292.58800000000002, + 293.02199999999999, + 293.45699999999999, + 293.89100000000002, + 294.32600000000002, + 294.75999999999999, + 295.19499999999999, + 295.63, + 296.065, + 296.5, + 296.935, + 297.37, + 297.80500000000001, + 298.24099999999999, + 298.67599999999999, + 299.11200000000002, + 299.548, + 299.983, + 300.41899999999998, + 300.85500000000002, + 301.291, + 301.72800000000001, + 302.16399999999999, + 302.60000000000002, + 303.036, + 303.47300000000001, + 303.91000000000003, + 304.346, + 304.78300000000002, + 305.22000000000003, + 305.65699999999998, + 306.09399999999999, + 306.53100000000001, + 306.96800000000002, + 307.40600000000001, + 307.84300000000002, + 308.27999999999997, + 308.71800000000002, + 309.15600000000001, + 309.59399999999999, + 310.03100000000001, + 310.46899999999999, + 310.90699999999998, + 311.346, + 311.78399999999999, + 312.22199999999998, + 312.661, + 313.09899999999999, + 313.53800000000001, + 313.976, + 314.41500000000002, + 314.85399999999998, + 315.29300000000001, + 315.73200000000003, + 316.17099999999999, + 316.61000000000001, + 317.05000000000001, + 317.48899999999998, + 317.92899999999997, + 318.36799999999999, + 328.36799999999999, + 338.36799999999999, + 348.36799999999999, + 358.36799999999999, + 368.36799999999999, + 378.36799999999999, + 388.36799999999999, + 398.36799999999999, + 408.36799999999999, + 418.36799999999999, + 428.36799999999999, + 438.36799999999999, + 448.36799999999999, + 458.36799999999999, + 468.36799999999999, + 478.36799999999999, + 488.36799999999999, + 498.36799999999999, + 508.36799999999999, + 518.36800000000005, + 528.36800000000005, + 538.36800000000005, + 548.36800000000005, + 558.36800000000005, + 568.36800000000005, + 578.36800000000005, + 588.36800000000005, + 598.36800000000005, + 608.36800000000005, + 618.36800000000005, + 628.36800000000005, + 638.36800000000005, + 648.36800000000005, + 658.36800000000005, + 668.36800000000005, + 678.36800000000005, + 688.36800000000005, + 698.36800000000005, + 708.36800000000005, + 718.36800000000005, + 728.36800000000005, + 738.36800000000005, + 748.36800000000005, + 758.36800000000005, + 768.36800000000005, + 778.36800000000005, + 788.36800000000005, + 798.36800000000005, + 808.36800000000005, + 818.36800000000005, + 828.36800000000005, + 838.36800000000005, + 848.36800000000005, + 858.36800000000005, + 868.36800000000005, + 878.36800000000005, + 888.36800000000005, + 898.36800000000005, + 908.36800000000005, + 918.36800000000005, + 928.36800000000005, + 938.36800000000005, + 948.36800000000005, + 958.36800000000005, + 968.36800000000005, + 978.36800000000005, + 988.36800000000005, + 998.36800000000005, + 1008.37, + 1018.37, + 1028.3699999999999, + 1038.3699999999999, + 1048.3699999999999, + 1058.3699999999999, + 1068.3699999999999, + 1078.3699999999999, + 1088.3699999999999, + 1098.3699999999999, + 1108.3699999999999 + ], + "values" : + [ + 0.001, + 1.0009999999999999, + 2.0009999999999999, + 3.0009999999999999, + 4.0010000000000003, + 5.0010000000000003, + 6.0010000000000003, + 7.0010000000000003, + 8.0009999999999994, + 9.0009999999999994, + 10.000999999999999, + 11.000999999999999, + 12.000999999999999, + 13.000999999999999, + 14.000999999999999, + 15.000999999999999, + 16.001000000000001, + 17.001000000000001, + 18.001000000000001, + 19.001000000000001, + 20.001000000000001, + 21.001000000000001, + 22.001000000000001, + 23.001000000000001, + 24.001000000000001, + 25.001000000000001, + 26.001000000000001, + 27.001000000000001, + 28.001000000000001, + 29.001000000000001, + 30.001000000000001, + 31.001000000000001, + 32.000999999999998, + 33.000999999999998, + 34.000999999999998, + 35.000999999999998, + 36.000999999999998, + 37.000999999999998, + 38.000999999999998, + 39.000999999999998, + 40.000999999999998, + 41.000999999999998, + 42.000999999999998, + 43.000999999999998, + 44.000999999999998, + 45.000999999999998, + 46.000999999999998, + 47.000999999999998, + 48.000999999999998, + 49.000999999999998, + 50.000999999999998, + 51.000999999999998, + 52.000999999999998, + 53.000999999999998, + 54.000999999999998, + 55.000999999999998, + 56.000999999999998, + 57.000999999999998, + 58.000999999999998, + 59.000999999999998, + 60.000999999999998, + 61.000999999999998, + 62.000999999999998, + 63.000999999999998, + 64.001000000000005, + 65.001000000000005, + 66.001000000000005, + 67.001000000000005, + 68.001000000000005, + 69.001000000000005, + 70.001000000000005, + 71.001000000000005, + 72.001000000000005, + 73.001000000000005, + 74.001000000000005, + 75.001000000000005, + 76.001000000000005, + 77.001000000000005, + 78.001000000000005, + 79.001000000000005, + 80.001000000000005, + 81.001000000000005, + 82.001000000000005, + 83.001000000000005, + 84.001000000000005, + 85.001000000000005, + 86.001000000000005, + 87.001000000000005, + 88.001000000000005, + 89.001000000000005, + 90.001000000000005, + 91.001000000000005, + 92.001000000000005, + 93.001000000000005, + 94.001000000000005, + 95.001000000000005, + 96.001000000000005, + 97.001000000000005, + 98.001000000000005, + 99.001000000000005, + 100.001, + 101.001, + 102.001, + 103.001, + 104.001, + 105.001, + 106.001, + 107.001, + 108.001, + 109.001, + 110.001, + 111.001, + 112.001, + 113.001, + 114.001, + 115.001, + 116.001, + 117.001, + 118.001, + 119.001, + 120.001, + 121.001, + 122.001, + 123.001, + 124.001, + 125.001, + 126.001, + 127.001, + 128.001, + 129.001, + 130.001, + 131.001, + 132.001, + 133.001, + 134.001, + 135.001, + 136.001, + 137.001, + 138.001, + 139.001, + 140.001, + 141.001, + 142.001, + 143.001, + 144.001, + 145.001, + 146.001, + 147.001, + 148.001, + 149.001, + 150.001, + 151.001, + 152.001, + 153.001, + 154.001, + 155.001, + 156.001, + 157.001, + 158.001, + 159.001, + 160.001, + 161.001, + 162.001, + 163.001, + 164.001, + 165.001, + 166.001, + 167.001, + 168.001, + 169.001, + 170.001, + 171.001, + 172.001, + 173.001, + 174.001, + 175.001, + 176.001, + 177.001, + 178.001, + 179.001, + 180.001, + 181.001, + 182.001, + 183.001, + 184.001, + 185.001, + 186.001, + 187.001, + 188.001, + 189.001, + 190.001, + 191.001, + 192.001, + 193.001, + 194.001, + 195.001, + 196.001, + 197.001, + 198.001, + 199.001, + 200.001, + 201.001, + 202.001, + 203.001, + 204.001, + 205.001, + 206.001, + 207.001, + 208.001, + 209.001, + 210.001, + 211.001, + 212.001, + 213.001, + 214.001, + 215.001, + 216.001, + 217.001, + 218.001, + 219.001, + 220.001, + 221.001, + 222.001, + 223.001, + 224.001, + 225.001, + 226.001, + 227.001, + 228.001, + 229.001, + 230.001, + 231.001, + 232.001, + 233.001, + 234.001, + 235.001, + 236.001, + 237.001, + 238.001, + 239.001, + 240.001, + 241.001, + 242.001, + 243.001, + 244.001, + 245.001, + 246.001, + 247.001, + 248.001, + 249.001, + 250.001, + 251.001, + 252.001, + 253.001, + 254.001, + 255.001, + 256.00099999999998, + 257.00099999999998, + 258.00099999999998, + 259.00099999999998, + 260.00099999999998, + 261.00099999999998, + 262.00099999999998, + 263.00099999999998, + 264.00099999999998, + 265.00099999999998, + 266.00099999999998, + 267.00099999999998, + 268.00099999999998, + 269.00099999999998, + 270.00099999999998, + 271.00099999999998, + 272.00099999999998, + 273.00099999999998, + 274.00099999999998, + 275.00099999999998, + 276.00099999999998, + 277.00099999999998, + 278.00099999999998, + 279.00099999999998, + 280.00099999999998, + 281.00099999999998, + 282.00099999999998, + 283.00099999999998, + 284.00099999999998, + 285.00099999999998, + 286.00099999999998, + 287.00099999999998, + 288.00099999999998, + 289.00099999999998, + 290.00099999999998, + 291.00099999999998, + 292.00099999999998, + 293.00099999999998, + 294.00099999999998, + 295.00099999999998, + 296.00099999999998, + 297.00099999999998, + 298.00099999999998, + 299.00099999999998, + 300.00099999999998, + 301.00099999999998, + 302.00099999999998, + 303.00099999999998, + 304.00099999999998, + 305.00099999999998, + 306.00099999999998, + 307.00099999999998, + 308.00099999999998, + 309.00099999999998, + 310.00099999999998, + 311.00099999999998, + 312.00099999999998, + 313.00099999999998, + 314.00099999999998, + 315.00099999999998, + 316.00099999999998, + 317.00099999999998, + 318.00099999999998, + 319.00099999999998, + 320.00099999999998, + 321.00099999999998, + 322.00099999999998, + 323.00099999999998, + 324.00099999999998, + 325.00099999999998, + 326.00099999999998, + 327.00099999999998, + 328.00099999999998, + 329.00099999999998, + 330.00099999999998, + 331.00099999999998, + 332.00099999999998, + 333.00099999999998, + 334.00099999999998, + 335.00099999999998, + 336.00099999999998, + 337.00099999999998, + 338.00099999999998, + 339.00099999999998, + 340.00099999999998, + 341.00099999999998, + 342.00099999999998, + 343.00099999999998, + 344.00099999999998, + 345.00099999999998, + 346.00099999999998, + 347.00099999999998, + 348.00099999999998, + 349.00099999999998, + 350.00099999999998, + 351.00099999999998, + 352.00099999999998, + 353.00099999999998, + 354.00099999999998, + 355.00099999999998, + 356.00099999999998, + 357.00099999999998, + 358.00099999999998, + 359.00099999999998, + 360.00099999999998, + 361.00099999999998, + 362.00099999999998, + 363.00099999999998, + 364.00099999999998, + 365.00099999999998, + 366.00099999999998, + 367.00099999999998, + 368.00099999999998, + 369.00099999999998, + 370.00099999999998, + 371.00099999999998, + 372.00099999999998, + 373.00099999999998, + 374.00099999999998, + 375.00099999999998, + 376.00099999999998, + 377.00099999999998, + 378.00099999999998, + 379.00099999999998, + 380.00099999999998, + 381.00099999999998, + 382.00099999999998, + 383.00099999999998, + 384.00099999999998, + 385.00099999999998, + 386.00099999999998, + 387.00099999999998, + 388.00099999999998, + 389.00099999999998, + 390.00099999999998, + 391.00099999999998, + 392.00099999999998, + 393.00099999999998, + 394.00099999999998, + 395.00099999999998, + 396.00099999999998, + 397.00099999999998, + 398.00099999999998, + 399.00099999999998, + 400.00099999999998, + 401.00099999999998, + 402.00099999999998, + 403.00099999999998, + 404.00099999999998, + 405.00099999999998, + 406.00099999999998, + 407.00099999999998, + 408.00099999999998, + 409.00099999999998, + 410.00099999999998, + 411.00099999999998, + 412.00099999999998, + 413.00099999999998, + 414.00099999999998, + 415.00099999999998, + 416.00099999999998, + 417.00099999999998, + 418.00099999999998, + 419.00099999999998, + 420.00099999999998, + 421.00099999999998, + 422.00099999999998, + 423.00099999999998, + 424.00099999999998, + 425.00099999999998, + 426.00099999999998, + 427.00099999999998, + 428.00099999999998, + 429.00099999999998, + 430.00099999999998, + 431.00099999999998, + 432.00099999999998, + 433.00099999999998, + 434.00099999999998, + 435.00099999999998, + 436.00099999999998, + 437.00099999999998, + 438.00099999999998, + 439.00099999999998, + 440.00099999999998, + 441.00099999999998, + 442.00099999999998, + 443.00099999999998, + 444.00099999999998, + 445.00099999999998, + 446.00099999999998, + 447.00099999999998, + 448.00099999999998, + 449.00099999999998, + 450.00099999999998, + 451.00099999999998, + 452.00099999999998, + 453.00099999999998, + 454.00099999999998, + 455.00099999999998, + 456.00099999999998, + 457.00099999999998, + 458.00099999999998, + 459.00099999999998, + 460.00099999999998, + 461.00099999999998, + 462.00099999999998, + 463.00099999999998, + 464.00099999999998, + 465.00099999999998, + 466.00099999999998, + 467.00099999999998, + 468.00099999999998, + 469.00099999999998, + 470.00099999999998, + 471.00099999999998, + 472.00099999999998, + 473.00099999999998, + 474.00099999999998, + 475.00099999999998, + 476.00099999999998, + 477.00099999999998, + 478.00099999999998, + 479.00099999999998, + 480.00099999999998, + 481.00099999999998, + 482.00099999999998, + 483.00099999999998, + 484.00099999999998, + 485.00099999999998, + 486.00099999999998, + 487.00099999999998, + 488.00099999999998, + 489.00099999999998, + 490.00099999999998, + 491.00099999999998, + 492.00099999999998, + 493.00099999999998, + 494.00099999999998, + 495.00099999999998, + 496.00099999999998, + 497.00099999999998, + 498.00099999999998, + 499.00099999999998, + 500.00099999999998, + 501.00099999999998, + 502.00099999999998, + 503.00099999999998, + 504.00099999999998, + 505.00099999999998, + 506.00099999999998, + 507.00099999999998, + 508.00099999999998, + 509.00099999999998, + 510.00099999999998, + 511.00099999999998, + 512.00099999999998, + 513.00099999999998, + 514.00099999999998, + 515.00099999999998, + 516.00099999999998, + 517.00099999999998, + 518.00099999999998, + 519.00099999999998, + 520.00099999999998, + 521.00099999999998, + 522.00099999999998, + 523.00099999999998, + 524.00099999999998, + 525.00099999999998, + 526.00099999999998, + 527.00099999999998, + 528.00099999999998, + 529.00099999999998, + 530.00099999999998, + 531.00099999999998, + 532.00099999999998, + 533.00099999999998, + 534.00099999999998, + 535.00099999999998, + 536.00099999999998, + 537.00099999999998, + 538.00099999999998, + 539.00099999999998, + 540.00099999999998, + 541.00099999999998, + 542.00099999999998, + 543.00099999999998, + 544.00099999999998, + 545.00099999999998, + 546.00099999999998, + 547.00099999999998, + 548.00099999999998, + 549.00099999999998, + 550.00099999999998, + 551.00099999999998, + 552.00099999999998, + 553.00099999999998, + 554.00099999999998, + 555.00099999999998, + 556.00099999999998, + 557.00099999999998, + 558.00099999999998, + 559.00099999999998, + 560.00099999999998, + 561.00099999999998, + 562.00099999999998, + 563.00099999999998, + 564.00099999999998, + 565.00099999999998, + 566.00099999999998, + 567.00099999999998, + 568.00099999999998, + 569.00099999999998, + 570.00099999999998, + 571.00099999999998, + 572.00099999999998, + 573.00099999999998, + 574.00099999999998, + 575.00099999999998, + 576.00099999999998, + 577.00099999999998, + 578.00099999999998, + 579.00099999999998, + 580.00099999999998, + 581.00099999999998, + 582.00099999999998, + 583.00099999999998, + 584.00099999999998, + 585.00099999999998, + 586.00099999999998, + 587.00099999999998, + 588.00099999999998, + 589.00099999999998, + 590.00099999999998, + 591.00099999999998, + 592.00099999999998, + 593.00099999999998, + 594.00099999999998, + 595.00099999999998, + 596.00099999999998, + 597.00099999999998, + 598.00099999999998, + 599.00099999999998, + 600.00099999999998, + 601.00099999999998, + 602.00099999999998, + 603.00099999999998, + 604.00099999999998, + 605.00099999999998, + 606.00099999999998, + 607.00099999999998, + 608.00099999999998, + 609.00099999999998, + 610.00099999999998, + 611.00099999999998, + 612.00099999999998, + 613.00099999999998, + 614.00099999999998, + 615.00099999999998, + 616.00099999999998, + 617.00099999999998, + 618.00099999999998, + 619.00099999999998, + 620.00099999999998, + 621.00099999999998, + 622.00099999999998, + 623.00099999999998, + 624.00099999999998, + 625.00099999999998, + 626.00099999999998, + 627.00099999999998, + 628.00099999999998, + 629.00099999999998, + 630.00099999999998, + 631.00099999999998, + 632.00099999999998, + 633.00099999999998, + 634.00099999999998, + 635.00099999999998, + 636.00099999999998, + 637.00099999999998, + 638.00099999999998, + 639.00099999999998, + 640.00099999999998, + 641.00099999999998, + 642.00099999999998, + 643.00099999999998, + 644.00099999999998, + 645.00099999999998, + 646.00099999999998, + 647.00099999999998, + 648.00099999999998, + 649.00099999999998, + 650.00099999999998, + 651.00099999999998, + 652.00099999999998, + 653.00099999999998, + 654.00099999999998, + 655.00099999999998, + 656.00099999999998, + 657.00099999999998, + 658.00099999999998, + 659.00099999999998, + 660.00099999999998, + 661.00099999999998, + 662.00099999999998, + 663.00099999999998, + 664.00099999999998, + 665.00099999999998, + 666.00099999999998, + 667.00099999999998, + 668.00099999999998, + 669.00099999999998, + 670.00099999999998, + 671.00099999999998, + 672.00099999999998, + 673.00099999999998, + 674.00099999999998, + 675.00099999999998, + 676.00099999999998, + 677.00099999999998, + 678.00099999999998, + 679.00099999999998, + 680.00099999999998, + 681.00099999999998, + 682.00099999999998, + 683.00099999999998, + 684.00099999999998, + 685.00099999999998, + 686.00099999999998, + 687.00099999999998, + 688.00099999999998, + 689.00099999999998, + 690.00099999999998, + 691.00099999999998, + 692.00099999999998, + 693.00099999999998, + 694.00099999999998, + 695.00099999999998, + 696.00099999999998, + 697.00099999999998, + 698.00099999999998, + 699.00099999999998, + 700.00099999999998, + 701.00099999999998, + 702.00099999999998, + 703.00099999999998, + 704.00099999999998, + 705.00099999999998, + 706.00099999999998, + 707.00099999999998, + 708.00099999999998, + 709.00099999999998, + 710.00099999999998, + 711.00099999999998, + 712.00099999999998, + 713.00099999999998, + 714.00099999999998, + 715.00099999999998, + 716.00099999999998, + 717.00099999999998, + 718.00099999999998, + 719.00099999999998, + 720.00099999999998, + 721.00099999999998, + 722.00099999999998, + 723.00099999999998, + 724.00099999999998, + 725.00099999999998, + 726.00099999999998, + 727.00099999999998, + 728.00099999999998, + 729.00099999999998, + 730.00099999999998, + 731.00099999999998, + 732.00099999999998, + 733.00099999999998, + 734.00099999999998, + 735.00099999999998, + 736.00099999999998, + 737.00099999999998, + 738.00099999999998, + 739.00099999999998, + 740.00099999999998, + 741.00099999999998, + 742.00099999999998, + 743.00099999999998, + 744.00099999999998, + 745.00099999999998, + 746.00099999999998, + 747.00099999999998, + 748.00099999999998, + 749.00099999999998, + 750.00099999999998, + 751.00099999999998, + 752.00099999999998, + 753.00099999999998, + 754.00099999999998, + 755.00099999999998, + 756.00099999999998, + 757.00099999999998, + 758.00099999999998, + 759.00099999999998, + 760.00099999999998, + 761.00099999999998, + 762.00099999999998, + 763.00099999999998, + 764.00099999999998, + 765.00099999999998, + 766.00099999999998, + 767.00099999999998, + 768.00099999999998, + 769.00099999999998, + 770.00099999999998, + 771.00099999999998, + 772.00099999999998, + 773.00099999999998, + 774.00099999999998, + 775.00099999999998, + 776.00099999999998, + 777.00099999999998, + 778.00099999999998, + 779.00099999999998, + 780.00099999999998, + 781.00099999999998, + 782.00099999999998, + 783.00099999999998, + 784.00099999999998, + 785.00099999999998, + 786.00099999999998, + 787.00099999999998, + 788.00099999999998, + 789.00099999999998, + 790.00099999999998, + 791.00099999999998, + 792.00099999999998, + 793.00099999999998, + 794.00099999999998, + 795.00099999999998, + 796.00099999999998, + 797.00099999999998, + 798.00099999999998, + 799.00099999999998, + 800.00099999999998, + 801.00099999999998, + 802.00099999999998, + 803.00099999999998, + 804.00099999999998, + 805.00099999999998, + 806.00099999999998, + 807.00099999999998, + 808.00099999999998, + 809.00099999999998, + 810.00099999999998, + 811.00099999999998, + 812.00099999999998, + 813.00099999999998, + 814.00099999999998, + 815.00099999999998, + 816.00099999999998, + 817.00099999999998, + 818.00099999999998, + 819.00099999999998, + 820.00099999999998, + 821.00099999999998, + 822.00099999999998, + 823.00099999999998, + 824.00099999999998, + 825.00099999999998, + 826.00099999999998, + 827.00099999999998, + 828.00099999999998, + 829.00099999999998, + 830.00099999999998, + 831.00099999999998, + 832.00099999999998, + 833.00099999999998, + 834.00099999999998, + 835.00099999999998, + 836.00099999999998, + 837.00099999999998, + 838.00099999999998, + 839.00099999999998, + 840.00099999999998, + 841.00099999999998, + 842.00099999999998, + 843.00099999999998, + 844.00099999999998, + 845.00099999999998, + 846.00099999999998, + 847.00099999999998, + 848.00099999999998, + 849.00099999999998, + 850.00099999999998, + 851.00099999999998, + 852.00099999999998, + 853.00099999999998, + 854.00099999999998, + 855.00099999999998, + 856.00099999999998, + 857.00099999999998, + 858.00099999999998, + 859.00099999999998, + 860.00099999999998, + 861.00099999999998, + 862.00099999999998, + 863.00099999999998, + 864.00099999999998, + 865.00099999999998, + 866.00099999999998, + 867.00099999999998, + 868.00099999999998, + 869.00099999999998, + 870.00099999999998, + 871.00099999999998, + 872.00099999999998, + 873.00099999999998, + 874.00099999999998, + 875.00099999999998, + 876.00099999999998, + 877.00099999999998, + 878.00099999999998, + 879.00099999999998, + 880.00099999999998, + 881.00099999999998, + 882.00099999999998, + 883.00099999999998, + 884.00099999999998, + 885.00099999999998, + 886.00099999999998, + 887.00099999999998, + 888.00099999999998, + 889.00099999999998, + 890.00099999999998, + 891.00099999999998, + 892.00099999999998, + 893.00099999999998, + 894.00099999999998, + 895.00099999999998, + 896.00099999999998, + 897.00099999999998, + 898.00099999999998, + 899.00099999999998, + 900.00099999999998, + 901.00099999999998, + 902.00099999999998, + 903.00099999999998, + 904.00099999999998, + 905.00099999999998, + 906.00099999999998, + 907.00099999999998, + 908.00099999999998, + 909.00099999999998, + 910.00099999999998, + 911.00099999999998, + 912.00099999999998, + 913.00099999999998, + 914.00099999999998, + 915.00099999999998, + 916.00099999999998, + 917.00099999999998, + 918.00099999999998, + 919.00099999999998, + 920.00099999999998, + 921.00099999999998, + 922.00099999999998, + 923.00099999999998, + 924.00099999999998, + 925.00099999999998, + 926.00099999999998, + 927.00099999999998, + 928.00099999999998, + 929.00099999999998, + 930.00099999999998, + 931.00099999999998, + 932.00099999999998, + 933.00099999999998, + 934.00099999999998, + 935.00099999999998, + 936.00099999999998, + 937.00099999999998, + 938.00099999999998, + 939.00099999999998, + 940.00099999999998, + 941.00099999999998, + 942.00099999999998, + 943.00099999999998, + 944.00099999999998, + 945.00099999999998, + 946.00099999999998, + 947.00099999999998, + 948.00099999999998, + 949.00099999999998, + 950.00099999999998, + 951.00099999999998, + 952.00099999999998, + 953.00099999999998, + 954.00099999999998, + 955.00099999999998, + 956.00099999999998, + 957.00099999999998, + 958.00099999999998, + 959.00099999999998, + 960.00099999999998, + 961.00099999999998, + 962.00099999999998, + 963.00099999999998, + 964.00099999999998, + 965.00099999999998, + 966.00099999999998, + 967.00099999999998, + 968.00099999999998, + 969.00099999999998, + 970.00099999999998, + 971.00099999999998, + 972.00099999999998, + 973.00099999999998, + 974.00099999999998, + 975.00099999999998, + 976.00099999999998, + 977.00099999999998, + 978.00099999999998, + 979.00099999999998, + 980.00099999999998, + 981.00099999999998, + 982.00099999999998, + 983.00099999999998, + 984.00099999999998, + 985.00099999999998, + 986.00099999999998, + 987.00099999999998, + 988.00099999999998, + 989.00099999999998, + 990.00099999999998, + 991.00099999999998, + 992.00099999999998, + 993.00099999999998, + 994.00099999999998, + 995.00099999999998, + 996.00099999999998, + 997.00099999999998, + 998.00099999999998, + 999.00099999999998, + 1020, + 1041, + 1062, + 1083, + 1104, + 1125, + 1146, + 1167, + 1188, + 1209, + 1230, + 1251, + 1272, + 1293, + 1314, + 1335, + 1356, + 1377, + 1398, + 1419, + 1440, + 1461, + 1482, + 1503, + 1524, + 1545, + 1566, + 1587, + 1608, + 1629, + 1650, + 1671, + 1692, + 1713, + 1734, + 1755, + 1776, + 1797, + 1818, + 1839, + 1860, + 1881, + 1902, + 1923, + 1944, + 1965, + 1986, + 2007, + 2028, + 2049, + 2070, + 2091, + 2112, + 2133, + 2154, + 2175, + 2196, + 2217, + 2238, + 2259, + 2280, + 2301, + 2322, + 2343, + 2364, + 2385, + 2406, + 2427, + 2448, + 2469, + 2490, + 2511, + 2532, + 2553, + 2574, + 2595, + 2616, + 2637, + 2658 + ] + }, + "name" : "ProtonRange", + "type" : "LinterpFunction" + }, + { + "data" : + { + "coords" : + [ + 8.2546999999999999e-07, + 2.4712600000000001e-05, + 6.6877800000000005e-05, + 0.00012592500000000001, + 0.00020052199999999999, + 0.00028981599999999998, + 0.00039143099999999997, + 0.00050559400000000005, + 0.00063447000000000004, + 0.00077489599999999996, + 0.00092809800000000003, + 0.00109325, + 0.0012695, + 0.00145776, + 0.00165638, + 0.0018660700000000001, + 0.0020862599999999999, + 0.0023164499999999998, + 0.00255718, + 0.0028073299999999998, + 0.0030673800000000002, + 0.0033368899999999999, + 0.00361554, + 0.0039036700000000001, + 0.0042004599999999996, + 0.0045062499999999998, + 0.0048203300000000003, + 0.00514212, + 0.0054720200000000002, + 0.00581045, + 0.0061578500000000003, + 0.0065132699999999998, + 0.0068758999999999999, + 0.0072460399999999996, + 0.0076240199999999996, + 0.0080101700000000005, + 0.0084037000000000001, + 0.0088039799999999994, + 0.0092112500000000007, + 0.0096257600000000006, + 0.010047800000000001, + 0.010476600000000001, + 0.010911799999999999, + 0.011353500000000001, + 0.011801900000000001, + 0.012257199999999999, + 0.012718999999999999, + 0.013186700000000001, + 0.013660500000000001, + 0.0141406, + 0.0146272, + 0.015118899999999999, + 0.0156148, + 0.016114799999999999, + 0.016619100000000001, + 0.017127699999999999, + 0.017640699999999999, + 0.018158199999999999, + 0.0186803, + 0.019206999999999998, + 0.0197384, + 0.0202746, + 0.020815699999999999, + 0.0213618, + 0.021912899999999999, + 0.022469200000000002, + 0.023030800000000001, + 0.023597799999999999, + 0.024170199999999999, + 0.024748300000000001, + 0.025332, + 0.0259215, + 0.026516899999999999, + 0.027118400000000001, + 0.027726000000000001, + 0.028339900000000001, + 0.028958500000000002, + 0.0295804, + 0.030205599999999999, + 0.030834199999999999, + 0.0314662, + 0.032101699999999997, + 0.032740699999999998, + 0.033383200000000002, + 0.034029200000000003, + 0.034678899999999999, + 0.035332200000000001, + 0.035989199999999999, + 0.036650000000000002, + 0.0373145, + 0.037982799999999997, + 0.038655000000000002, + 0.039331100000000001, + 0.040011199999999997, + 0.040695299999999997, + 0.041383400000000001, + 0.042075599999999998, + 0.042771999999999998, + 0.043472499999999997, + 0.044177300000000003, + 0.044879299999999997, + 0.045591699999999999, + 0.046306699999999999, + 0.047024400000000001, + 0.047744700000000001, + 0.0484676, + 0.049193300000000002, + 0.049921599999999997, + 0.050652599999999999, + 0.051386399999999999, + 0.052123000000000003, + 0.052862300000000001, + 0.053604499999999999, + 0.054349399999999999, + 0.055097199999999999, + 0.055847899999999999, + 0.056601499999999999, + 0.057357999999999999, + 0.0581174, + 0.058879800000000003, + 0.0596451, + 0.060413500000000002, + 0.0611849, + 0.061959300000000002, + 0.062736799999999995, + 0.063517400000000002, + 0.064300599999999999, + 0.065085799999999999, + 0.065873000000000001, + 0.066662299999999994, + 0.067453700000000005, + 0.068247199999999994, + 0.069042699999999999, + 0.069840399999999997, + 0.0706402, + 0.071442099999999994, + 0.072246199999999997, + 0.073052400000000003, + 0.073860800000000004, + 0.074671299999999996, + 0.075484099999999998, + 0.076299000000000006, + 0.077116199999999996, + 0.077935599999999994, + 0.078757199999999999, + 0.079581100000000002, + 0.080407199999999998, + 0.081235600000000005, + 0.082066299999999995, + 0.082899299999999995, + 0.083734600000000006, + 0.084571800000000003, + 0.085410700000000006, + 0.086251099999999997, + 0.087093199999999996, + 0.087936899999999998, + 0.088782200000000006, + 0.089629200000000006, + 0.090477799999999997, + 0.091328099999999995, + 0.092179999999999998, + 0.093033599999999994, + 0.093888899999999997, + 0.094745800000000005, + 0.095604400000000006, + 0.0964647, + 0.097326800000000005, + 0.0981905, + 0.099055900000000002, + 0.099923100000000001, + 0.10079200000000001, + 0.101663, + 0.102535, + 0.103409, + 0.104285, + 0.10516200000000001, + 0.106042, + 0.106922, + 0.107804, + 0.108686, + 0.109571, + 0.110456, + 0.111343, + 0.112231, + 0.11312, + 0.114011, + 0.11490300000000001, + 0.115797, + 0.116691, + 0.117587, + 0.11848400000000001, + 0.119383, + 0.120283, + 0.121184, + 0.122087, + 0.122991, + 0.12389600000000001, + 0.124803, + 0.12571099999999999, + 0.12662000000000001, + 0.127522, + 0.12843399999999999, + 0.12934699999999999, + 0.13026099999999999, + 0.13117599999999999, + 0.13209099999999999, + 0.13300799999999999, + 0.13392599999999999, + 0.13484499999999999, + 0.135765, + 0.136686, + 0.13760800000000001, + 0.13853099999999999, + 0.139455, + 0.14038, + 0.14130599999999999, + 0.142234, + 0.14316200000000001, + 0.144091, + 0.14502100000000001, + 0.145952, + 0.14688499999999999, + 0.147818, + 0.148752, + 0.14968799999999999, + 0.15062400000000001, + 0.151562, + 0.1525, + 0.15343899999999999, + 0.15437899999999999, + 0.15531900000000001, + 0.15626100000000001, + 0.15720300000000001, + 0.15814600000000001, + 0.15909000000000001, + 0.16003400000000001, + 0.16097900000000001, + 0.16192599999999999, + 0.16287299999999999, + 0.16381999999999999, + 0.164769, + 0.165718, + 0.16666800000000001, + 0.16761899999999999, + 0.168571, + 0.16952400000000001, + 0.17047699999999999, + 0.171431, + 0.17238600000000001, + 0.173342, + 0.17429900000000001, + 0.175256, + 0.17621400000000001, + 0.177172, + 0.17813100000000001, + 0.179091, + 0.18005099999999999, + 0.18101200000000001, + 0.181973, + 0.18293499999999999, + 0.183897, + 0.18486, + 0.18582399999999999, + 0.18678800000000001, + 0.187753, + 0.188718, + 0.18968399999999999, + 0.19064999999999999, + 0.19161700000000001, + 0.19258500000000001, + 0.193553, + 0.194522, + 0.195491, + 0.196461, + 0.197431, + 0.198402, + 0.199374, + 0.200346, + 0.201319, + 0.202292, + 0.203266, + 0.20424100000000001, + 0.20521600000000001, + 0.20619199999999999, + 0.20716799999999999, + 0.208145, + 0.209122, + 0.21010000000000001, + 0.21107899999999999, + 0.212058, + 0.21303800000000001, + 0.21401800000000001, + 0.214999, + 0.21598100000000001, + 0.21696299999999999, + 0.217946, + 0.21892900000000001, + 0.219913, + 0.22089800000000001, + 0.221883, + 0.222859, + 0.22384499999999999, + 0.224831, + 0.22581799999999999, + 0.22680500000000001, + 0.227793, + 0.22878100000000001, + 0.229769, + 0.23075799999999999, + 0.23174600000000001, + 0.232736, + 0.23372499999999999, + 0.23471500000000001, + 0.235705, + 0.23669599999999999, + 0.23768700000000001, + 0.238678, + 0.23966999999999999, + 0.24066199999999999, + 0.24165400000000001, + 0.242647, + 0.24364, + 0.24463299999999999, + 0.24562700000000001, + 0.24662100000000001, + 0.247616, + 0.24861, + 0.24960499999999999, + 0.25060100000000002, + 0.25159700000000002, + 0.25259300000000001, + 0.25358900000000001, + 0.25458599999999998, + 0.255583, + 0.256581, + 0.257579, + 0.258577, + 0.259575, + 0.26057399999999997, + 0.261573, + 0.262573, + 0.263573, + 0.264573, + 0.26557399999999998, + 0.26657500000000001, + 0.26757599999999998, + 0.26857799999999998, + 0.26957999999999999, + 0.27058199999999999, + 0.27158500000000002, + 0.272588, + 0.273592, + 0.27459499999999998, + 0.27559899999999998, + 0.27660299999999999, + 0.27760699999999999, + 0.27861200000000003, + 0.27961599999999998, + 0.28062100000000001, + 0.28162599999999999, + 0.28263100000000002, + 0.283636, + 0.28464200000000001, + 0.28564800000000001, + 0.28665299999999999, + 0.28766000000000003, + 0.28866599999999998, + 0.28967199999999999, + 0.29067900000000002, + 0.291686, + 0.29269299999999998, + 0.29370099999999999, + 0.29470800000000003, + 0.29571599999999998, + 0.29672399999999999, + 0.297732, + 0.29874000000000001, + 0.29974899999999999, + 0.300757, + 0.30176599999999998, + 0.30277500000000002, + 0.30378500000000003, + 0.30479400000000001, + 0.30580400000000002, + 0.30681399999999998, + 0.30782399999999999, + 0.308834, + 0.30984499999999998, + 0.31085499999999999, + 0.31186599999999998, + 0.31287700000000002, + 0.31388899999999997, + 0.31490000000000001, + 0.31591200000000003, + 0.31692399999999998, + 0.317936, + 0.31894800000000001, + 0.319961, + 0.32097300000000001, + 0.32198599999999999, + 0.32298900000000003, + 0.32400299999999999, + 0.32501600000000003, + 0.32602900000000001, + 0.32704299999999997, + 0.32805699999999999, + 0.329071, + 0.33008500000000002, + 0.33109899999999998, + 0.33211299999999999, + 0.33312700000000001, + 0.33414199999999999, + 0.33515600000000001, + 0.336171, + 0.33718500000000001, + 0.3382, + 0.33921499999999999, + 0.34022999999999998, + 0.34124599999999999, + 0.34226099999999998, + 0.34327600000000003, + 0.34429199999999999, + 0.345308, + 0.34632299999999999, + 0.34733900000000001, + 0.34835500000000003, + 0.34937099999999999, + 0.35038799999999998, + 0.35140399999999999, + 0.35242099999999998, + 0.353437, + 0.35445399999999999, + 0.35547099999999998, + 0.35648800000000003, + 0.35750500000000002, + 0.35852200000000001, + 0.359539, + 0.36055700000000002, + 0.36157400000000001, + 0.36259200000000003, + 0.36360900000000002, + 0.36462699999999998, + 0.365645, + 0.36666300000000002, + 0.36768200000000001, + 0.36870000000000003, + 0.36971799999999999, + 0.37073699999999998, + 0.37175599999999998, + 0.37277399999999999, + 0.37379299999999999, + 0.37481199999999998, + 0.37583100000000003, + 0.37685000000000002, + 0.37786900000000001, + 0.37888899999999998, + 0.37990800000000002, + 0.38092700000000002, + 0.38194600000000001, + 0.382965, + 0.38398399999999999, + 0.38500400000000001, + 0.38602300000000001, + 0.387042, + 0.38806200000000002, + 0.38908100000000001, + 0.3901, + 0.39112000000000002, + 0.39213900000000002, + 0.39315899999999998, + 0.39417799999999997, + 0.39519799999999999, + 0.39621800000000001, + 0.39723700000000001, + 0.39825700000000003, + 0.39927699999999999, + 0.40029599999999999, + 0.40131600000000001, + 0.40233600000000003, + 0.40335599999999999, + 0.40437499999999998, + 0.40539500000000001, + 0.40641500000000003, + 0.40743499999999999, + 0.40845500000000001, + 0.40947499999999998, + 0.410495, + 0.41151500000000002, + 0.41253499999999999, + 0.41355500000000001, + 0.41457500000000003, + 0.41559499999999999, + 0.41661500000000001, + 0.41763600000000001, + 0.41865599999999997, + 0.41967599999999999, + 0.42069600000000001, + 0.42171700000000001, + 0.42273699999999997, + 0.42375699999999999, + 0.42476799999999998, + 0.484871, + 0.54477399999999998, + 0.60447700000000004, + 0.66398100000000004, + 0.72323400000000004, + 0.78211299999999995, + 0.84061600000000003, + 0.89875000000000005, + 0.95651299999999995, + 1.01389, + 1.0708899999999999, + 1.1274999999999999, + 1.18374, + 1.2396100000000001, + 1.29511, + 1.3502400000000001, + 1.405, + 1.4594199999999999, + 1.5135000000000001, + 1.56725, + 1.62066, + 1.6737599999999999, + 1.72654, + 1.77902, + 1.8311900000000001, + 1.8830499999999999, + 1.9346300000000001, + 1.9859199999999999, + 2.0369199999999998, + 2.0876399999999999, + 2.1381000000000001, + 2.1882899999999998, + 2.2382200000000001, + 2.2879, + 2.3373300000000001, + 2.3865099999999999, + 2.4354499999999999, + 2.4841500000000001, + 2.5326200000000001, + 2.5808499999999999, + 2.6288499999999999, + 2.6766100000000002, + 2.7241499999999998, + 2.7714799999999999, + 2.8186, + 2.8654999999999999, + 2.9121999999999999, + 2.9586899999999998, + 3.0049800000000002, + 3.0510600000000001, + 3.09694, + 3.14263, + 3.1881300000000001, + 3.2334399999999999, + 3.2785500000000001, + 3.3234900000000001, + 3.3682300000000001, + 3.4127999999999998, + 3.4571800000000001, + 3.5013800000000002, + 3.5453999999999999, + 3.5892599999999999, + 3.6329400000000001, + 3.6764600000000001, + 3.7198000000000002, + 3.7629800000000002, + 3.80599, + 3.84884, + 3.8915299999999999, + 3.9340600000000001, + 3.9764300000000001, + 4.0186500000000001, + 4.0607199999999999, + 4.1026300000000004, + 4.1443899999999996, + 4.1859999999999999, + 4.2274700000000003, + 4.2687900000000001, + 4.3099699999999999, + 4.3510099999999996, + 4.3919199999999998, + 4.4326800000000004, + 4.4733200000000002, + 4.5138100000000003, + 4.5541700000000001, + 4.5944000000000003, + 4.6344900000000004, + 4.6744599999999998, + 4.7142900000000001, + 4.7539899999999999, + 4.7935600000000003, + 4.8330000000000002, + 4.8723200000000002, + 4.9115099999999998, + 4.9505699999999999, + 4.9895100000000001, + 5.0283199999999999, + 5.0670099999999998, + 5.1055700000000002, + 5.1440200000000003, + 5.1823399999999999, + 5.2205399999999997, + 5.2586199999999996, + 5.2965900000000001, + 5.3344300000000002, + 5.37216, + 5.40977, + 5.44726, + 5.4846399999999997, + 5.5219100000000001, + 5.5590599999999997, + 5.5960900000000002, + 5.6330200000000001, + 5.6698300000000001, + 5.7065299999999999, + 5.7431200000000002, + 5.7796000000000003, + 5.8159700000000001, + 5.8522299999999996, + 5.8883900000000002, + 5.9244500000000002, + 5.9603999999999999, + 5.9962600000000004, + 6.0320099999999996, + 6.0676600000000001, + 6.1032099999999998, + 6.1386700000000003, + 6.1740300000000001, + 6.2092900000000002, + 6.2444499999999996, + 6.2795100000000001, + 6.3144799999999996, + 6.3493500000000003, + 6.3841299999999999, + 6.4188099999999997, + 6.4534000000000002, + 6.4878900000000002, + 6.5223000000000004, + 6.55661, + 6.5908199999999999, + 6.6249500000000001, + 6.6589799999999997, + 6.6929299999999996, + 6.7267799999999998, + 6.7605500000000003, + 6.7942200000000001, + 6.8278100000000004, + 6.8613, + 6.8947099999999999, + 6.9280400000000002, + 6.9612699999999998, + 6.9944199999999999, + 7.0274900000000002, + 7.06046, + 7.0933599999999997, + 7.1261599999999996, + 7.1588900000000004, + 7.1915300000000002, + 7.2240799999999998, + 7.2565600000000003, + 7.2889499999999998, + 7.3212599999999997, + 7.3534899999999999, + 7.3856400000000004, + 7.4177200000000001, + 7.4497099999999996, + 7.48163, + 7.5134800000000004, + 7.5452399999999997, + 7.5769299999999999, + 7.6085399999999996, + 7.6400800000000002, + 7.6715400000000002, + 7.7029199999999998, + 7.7342399999999998, + 7.7654699999999997, + 7.79664, + 7.8277299999999999, + 7.8587400000000001, + 7.8896899999999999, + 7.92056, + 7.9513600000000002, + 7.9820799999999998, + 8.0127400000000009, + 8.0433199999999996, + 8.0738400000000006, + 8.1042799999999993, + 8.1346500000000006, + 8.1649600000000007, + 8.1951900000000002, + 8.2253500000000006, + 8.2554499999999997, + 8.2854700000000001, + 8.3154299999999992, + 8.3453199999999992, + 8.3751499999999997, + 8.4048999999999996, + 8.43459, + 8.4642099999999996, + 8.49376, + 8.5232500000000009, + 8.5526700000000009, + 8.5820299999999996, + 8.6113199999999992, + 8.6405499999999993, + 8.6697199999999999, + 8.6988199999999996, + 8.7278599999999997, + 8.7568300000000008, + 8.7857400000000005, + 8.8145900000000008, + 8.8433799999999998, + 8.8721099999999993, + 8.9007799999999992, + 8.9293800000000001, + 8.9579299999999993, + 8.9864099999999993, + 9.0148299999999999, + 9.0431899999999992, + 9.0715000000000003, + 9.0997400000000006, + 9.1279299999999992, + 9.1560500000000005, + 9.1841200000000001, + 9.2121200000000005, + 9.2400699999999993, + 9.2679600000000004, + 9.2957999999999998, + 9.3235700000000001, + 9.3512900000000005, + 9.3789499999999997, + 9.4065600000000007, + 9.4341000000000008, + 9.4615899999999993, + 9.4890299999999996, + 9.5164100000000005, + 9.54373, + 9.5709999999999997, + 9.5982099999999999, + 9.6253700000000002, + 9.6524699999999992, + 9.6795200000000001, + 9.7065099999999997, + 9.7334499999999995, + 9.7603299999999997, + 9.7871600000000001, + 9.8139400000000006, + 9.8406699999999994, + 9.8673400000000004, + 9.8939599999999999, + 9.9205299999999994, + 9.9470500000000008, + 9.9735200000000006, + 9.9999300000000009, + 10.026300000000001, + 10.0526, + 10.078900000000001, + 10.1051, + 10.1312, + 10.157400000000001, + 10.183400000000001, + 10.2094, + 10.2354, + 10.2613, + 10.2872, + 10.313000000000001, + 10.338699999999999, + 10.3645, + 10.3901, + 10.415699999999999, + 10.4413, + 10.466799999999999, + 10.4923, + 10.5177, + 10.543100000000001, + 10.5685, + 10.5937, + 10.619, + 10.6442, + 10.6693, + 10.6944, + 10.7195, + 10.7445, + 10.769399999999999, + 10.7944, + 10.8192, + 10.844099999999999, + 10.8688, + 10.893599999999999, + 10.9183, + 10.9429, + 10.967499999999999, + 10.992100000000001, + 11.0166, + 11.0411, + 11.0655, + 11.0899, + 11.1142, + 11.138500000000001, + 11.162800000000001, + 11.186999999999999, + 11.2112, + 11.235300000000001, + 11.259399999999999, + 11.2834, + 11.307399999999999, + 11.3314, + 11.3553, + 11.379200000000001, + 11.4031, + 11.4269, + 11.4506, + 11.474299999999999, + 11.497999999999999, + 11.521599999999999, + 11.545199999999999, + 11.5688, + 11.5923, + 11.6158, + 11.639200000000001, + 11.662599999999999, + 11.686, + 11.709300000000001, + 11.7326, + 11.755800000000001, + 11.779, + 11.802199999999999, + 11.8253, + 11.8484, + 11.8714, + 11.894399999999999, + 11.917400000000001, + 11.940300000000001, + 11.963200000000001, + 11.9861, + 12.008900000000001, + 12.031700000000001, + 12.054399999999999, + 12.0771, + 12.0998, + 12.122400000000001, + 12.145, + 12.1676, + 12.190099999999999, + 12.2126, + 12.235099999999999, + 12.2575, + 12.2799, + 12.302199999999999, + 12.3245, + 12.3468, + 12.369, + 12.3912, + 12.413399999999999, + 12.435499999999999, + 12.457599999999999, + 12.479699999999999, + 12.5017, + 12.5237, + 12.5456, + 12.567600000000001, + 12.589399999999999, + 12.6113, + 12.633100000000001, + 12.6549, + 12.676600000000001, + 12.698399999999999, + 12.720000000000001, + 12.7417, + 12.763299999999999, + 12.7849, + 12.8064, + 12.827999999999999, + 12.849399999999999, + 12.870900000000001, + 12.892300000000001, + 12.9137, + 12.935, + 12.9564, + 12.977600000000001, + 12.998900000000001, + 13.020099999999999, + 13.0413, + 13.0625, + 13.083600000000001, + 13.104699999999999, + 13.1258, + 13.146800000000001, + 13.1678, + 13.188700000000001, + 13.2097, + 13.230600000000001, + 13.2515, + 13.2723, + 13.293100000000001, + 13.3139, + 13.3346, + 13.355399999999999, + 13.376099999999999, + 13.396699999999999, + 13.417299999999999, + 13.437900000000001, + 13.458500000000001, + 13.478999999999999, + 13.499499999999999, + 13.52, + 13.5405, + 13.5609, + 13.581300000000001, + 13.601599999999999, + 13.622, + 13.642200000000001, + 13.6625, + 13.6828, + 13.702999999999999, + 13.723100000000001, + 13.7433, + 13.763400000000001, + 13.7835, + 13.803599999999999, + 13.823600000000001, + 13.8436, + 13.8636, + 13.883599999999999, + 13.903499999999999, + 13.923400000000001, + 13.943300000000001, + 13.963100000000001, + 13.982900000000001, + 14.002700000000001, + 14.022399999999999, + 14.042199999999999, + 14.0619, + 14.0816, + 14.1012, + 14.120799999999999, + 14.1404, + 14.16, + 14.179500000000001, + 14.199, + 14.218500000000001, + 14.238, + 14.257400000000001, + 14.2768, + 14.296200000000001, + 14.3155, + 14.3348, + 14.354100000000001, + 14.3734, + 14.3927, + 14.411899999999999, + 14.431100000000001, + 14.450200000000001, + 14.4694, + 14.4885, + 14.5076, + 14.5266, + 14.5457, + 14.5647, + 14.5837, + 14.602600000000001, + 14.621600000000001, + 14.640499999999999, + 14.6594, + 14.6782, + 14.697100000000001, + 14.7159, + 14.7346, + 14.753399999999999, + 14.7721, + 14.790800000000001, + 14.8095, + 14.828200000000001, + 14.8468, + 14.865399999999999, + 14.884, + 14.9026, + 14.921099999999999, + 14.9396, + 14.9581, + 14.976599999999999, + 14.994999999999999, + 15.013400000000001, + 15.0318, + 15.0502, + 15.0685, + 15.0868, + 15.1051, + 15.1234, + 15.1417, + 15.1599, + 15.178100000000001, + 15.196300000000001, + 15.214399999999999, + 15.2326, + 15.2507, + 15.268700000000001, + 15.286799999999999, + 15.3048, + 25.3048, + 35.3048, + 45.3048, + 55.3048, + 65.3048, + 75.3048, + 85.3048, + 95.3048, + 105.30500000000001, + 115.30500000000001, + 125.30500000000001, + 135.30500000000001, + 145.30500000000001, + 155.30500000000001, + 165.30500000000001, + 175.30500000000001, + 185.30500000000001, + 195.30500000000001, + 205.30500000000001, + 215.30500000000001, + 225.30500000000001, + 235.30500000000001, + 245.30500000000001, + 255.30500000000001, + 265.30500000000001, + 275.30500000000001, + 285.30500000000001, + 295.30500000000001, + 305.30500000000001, + 315.30500000000001, + 325.30500000000001, + 335.30500000000001, + 345.30500000000001, + 355.30500000000001, + 365.30500000000001, + 375.30500000000001, + 385.30500000000001, + 395.30500000000001, + 405.30500000000001, + 415.30500000000001, + 425.30500000000001, + 435.30500000000001, + 445.30500000000001, + 455.30500000000001, + 465.30500000000001, + 475.30500000000001, + 485.30500000000001, + 495.30500000000001, + 505.30500000000001, + 515.30499999999995, + 525.30499999999995, + 535.30499999999995, + 545.30499999999995, + 555.30499999999995, + 565.30499999999995, + 575.30499999999995, + 585.30499999999995, + 595.30499999999995, + 605.30499999999995, + 615.30499999999995, + 625.30499999999995, + 635.30499999999995, + 645.30499999999995, + 655.30499999999995, + 665.30499999999995, + 675.30499999999995, + 685.30499999999995, + 695.30499999999995, + 705.30499999999995, + 715.30499999999995, + 725.30499999999995, + 735.30499999999995, + 745.30499999999995, + 755.30499999999995, + 765.30499999999995, + 775.30499999999995, + 785.30499999999995, + 795.30499999999995, + 805.30499999999995, + 815.30499999999995, + 825.30499999999995, + 835.30499999999995, + 845.30499999999995, + 855.30499999999995, + 865.30499999999995, + 875.30499999999995, + 885.30499999999995, + 895.30499999999995, + 905.30499999999995, + 915.30499999999995, + 925.30499999999995, + 935.30499999999995, + 945.30499999999995, + 955.30499999999995, + 965.30499999999995, + 975.30499999999995, + 985.30499999999995, + 995.30499999999995, + 1005.3, + 1015.3, + 1025.3, + 1035.3, + 1045.3, + 1055.3, + 1065.3, + 1075.3, + 1085.3, + 1095.3, + 1105.3 + ], + "values" : + [ + 0.0001, + 0.0020998000000000002, + 0.0040996000000000001, + 0.0060993999999999996, + 0.0080992000000000008, + 0.010099, + 0.0120988, + 0.014098599999999999, + 0.016098399999999999, + 0.018098199999999998, + 0.020098000000000001, + 0.022097800000000001, + 0.0240976, + 0.0260974, + 0.028097199999999999, + 0.030096999999999999, + 0.032096800000000002, + 0.034096599999999998, + 0.036096400000000001, + 0.038096199999999997, + 0.040096, + 0.042095800000000003, + 0.044095599999999999, + 0.046095400000000002, + 0.048095199999999998, + 0.050095000000000001, + 0.052094799999999997, + 0.0540946, + 0.056094400000000003, + 0.058094199999999999, + 0.060094000000000002, + 0.062093799999999998, + 0.064093600000000001, + 0.066093399999999997, + 0.068093200000000006, + 0.070093000000000003, + 0.072092799999999999, + 0.074092599999999995, + 0.076092400000000004, + 0.078092200000000001, + 0.080091999999999997, + 0.082091800000000006, + 0.084091600000000002, + 0.086091399999999998, + 0.088091199999999995, + 0.090091000000000004, + 0.0920908, + 0.094090599999999996, + 0.096090400000000006, + 0.098090200000000002, + 0.10009, + 0.10209, + 0.10409, + 0.106089, + 0.108089, + 0.11008900000000001, + 0.11208899999999999, + 0.114089, + 0.116088, + 0.118088, + 0.120088, + 0.122088, + 0.124088, + 0.126087, + 0.12808700000000001, + 0.13008700000000001, + 0.13208700000000001, + 0.13408700000000001, + 0.13608600000000001, + 0.13808599999999999, + 0.14008599999999999, + 0.14208599999999999, + 0.14408599999999999, + 0.14608499999999999, + 0.14808499999999999, + 0.150085, + 0.152085, + 0.154085, + 0.156084, + 0.158084, + 0.160084, + 0.16208400000000001, + 0.16408400000000001, + 0.16608300000000001, + 0.16808300000000001, + 0.17008300000000001, + 0.17208300000000001, + 0.17408299999999999, + 0.17608199999999999, + 0.17808199999999999, + 0.18008199999999999, + 0.18208199999999999, + 0.184082, + 0.186081, + 0.188081, + 0.190081, + 0.192081, + 0.194081, + 0.19608, + 0.19808000000000001, + 0.20008000000000001, + 0.20208000000000001, + 0.20408000000000001, + 0.20607900000000001, + 0.20807899999999999, + 0.21007899999999999, + 0.21207899999999999, + 0.21407899999999999, + 0.21607799999999999, + 0.21807799999999999, + 0.220078, + 0.222078, + 0.224078, + 0.226077, + 0.228077, + 0.230077, + 0.23207700000000001, + 0.23407700000000001, + 0.23607600000000001, + 0.23807600000000001, + 0.24007600000000001, + 0.24207600000000001, + 0.24407599999999999, + 0.24607499999999999, + 0.24807499999999999, + 0.25007499999999999, + 0.25207499999999999, + 0.254075, + 0.25607400000000002, + 0.25807400000000003, + 0.26007400000000003, + 0.26207399999999997, + 0.26407399999999998, + 0.266073, + 0.26807300000000001, + 0.27007300000000001, + 0.27207300000000001, + 0.27407300000000001, + 0.27607199999999998, + 0.27807199999999999, + 0.28007199999999999, + 0.28207199999999999, + 0.28407199999999999, + 0.28607100000000002, + 0.28807100000000002, + 0.29007100000000002, + 0.29207100000000003, + 0.29407100000000003, + 0.29607, + 0.29807, + 0.30007, + 0.30207000000000001, + 0.30407000000000001, + 0.30606899999999998, + 0.30806899999999998, + 0.31006899999999998, + 0.31206899999999999, + 0.31406899999999999, + 0.31606800000000002, + 0.31806800000000002, + 0.32006800000000002, + 0.32206800000000002, + 0.32406800000000002, + 0.326067, + 0.328067, + 0.330067, + 0.332067, + 0.334067, + 0.33606599999999998, + 0.33806599999999998, + 0.34006599999999998, + 0.34206599999999998, + 0.34406599999999998, + 0.34606500000000001, + 0.34806500000000001, + 0.35006500000000002, + 0.35206500000000002, + 0.35406500000000002, + 0.35606399999999999, + 0.35806399999999999, + 0.360064, + 0.362064, + 0.364064, + 0.36606300000000003, + 0.36806299999999997, + 0.37006299999999998, + 0.37206299999999998, + 0.37406299999999998, + 0.37606200000000001, + 0.37806200000000001, + 0.38006200000000001, + 0.38206200000000001, + 0.38406200000000001, + 0.38606099999999999, + 0.38806099999999999, + 0.39006099999999999, + 0.39206099999999999, + 0.39406099999999999, + 0.39606000000000002, + 0.39806000000000002, + 0.40006000000000003, + 0.40205999999999997, + 0.40405999999999997, + 0.406059, + 0.40805900000000001, + 0.41005900000000001, + 0.41205900000000001, + 0.41405900000000001, + 0.41605799999999998, + 0.41805799999999999, + 0.42005799999999999, + 0.42205799999999999, + 0.42405799999999999, + 0.42605700000000002, + 0.42805700000000002, + 0.43005700000000002, + 0.43205700000000002, + 0.43405700000000003, + 0.436056, + 0.438056, + 0.440056, + 0.442056, + 0.44405600000000001, + 0.44605499999999998, + 0.44805499999999998, + 0.45005499999999998, + 0.45205499999999998, + 0.45405499999999999, + 0.45605400000000001, + 0.45805400000000002, + 0.46005400000000002, + 0.46205400000000002, + 0.46405400000000002, + 0.466053, + 0.468053, + 0.470053, + 0.472053, + 0.474053, + 0.47605199999999998, + 0.47805199999999998, + 0.48005199999999998, + 0.48205199999999998, + 0.48405199999999998, + 0.48605100000000001, + 0.48805100000000001, + 0.49005100000000001, + 0.49205100000000002, + 0.49405100000000002, + 0.49604999999999999, + 0.49804999999999999, + 0.50004999999999999, + 0.50205, + 0.50405, + 0.50604899999999997, + 0.50804899999999997, + 0.51004899999999997, + 0.51204899999999998, + 0.51404899999999998, + 0.51604799999999995, + 0.51804799999999995, + 0.52004799999999995, + 0.52204799999999996, + 0.52404799999999996, + 0.52604700000000004, + 0.52804700000000004, + 0.53004700000000005, + 0.53204700000000005, + 0.53404700000000005, + 0.53604600000000002, + 0.53804600000000002, + 0.54004600000000003, + 0.54204600000000003, + 0.54404600000000003, + 0.546045, + 0.548045, + 0.55004500000000001, + 0.55204500000000001, + 0.55404500000000001, + 0.55604399999999998, + 0.55804399999999998, + 0.56004399999999999, + 0.56204399999999999, + 0.56404399999999999, + 0.56604299999999996, + 0.56804299999999996, + 0.57004299999999997, + 0.57204299999999997, + 0.57404299999999997, + 0.57604200000000005, + 0.57804199999999994, + 0.58004199999999995, + 0.58204199999999995, + 0.58404199999999995, + 0.58604100000000003, + 0.58804100000000004, + 0.59004100000000004, + 0.59204100000000004, + 0.59404100000000004, + 0.59604000000000001, + 0.59804000000000002, + 0.60004000000000002, + 0.60204000000000002, + 0.60404000000000002, + 0.60603899999999999, + 0.608039, + 0.610039, + 0.612039, + 0.614039, + 0.61603799999999997, + 0.61803799999999998, + 0.62003799999999998, + 0.62203799999999998, + 0.62403799999999998, + 0.62603699999999995, + 0.62803699999999996, + 0.63003699999999996, + 0.63203699999999996, + 0.63403699999999996, + 0.63603600000000005, + 0.63803600000000005, + 0.64003600000000005, + 0.64203600000000005, + 0.64403600000000005, + 0.64603500000000003, + 0.64803500000000003, + 0.65003500000000003, + 0.65203500000000003, + 0.65403500000000003, + 0.65603400000000001, + 0.65803400000000001, + 0.66003400000000001, + 0.66203400000000001, + 0.66403400000000001, + 0.66603299999999999, + 0.66803299999999999, + 0.67003299999999999, + 0.67203299999999999, + 0.67403299999999999, + 0.67603199999999997, + 0.67803199999999997, + 0.68003199999999997, + 0.68203199999999997, + 0.68403199999999997, + 0.68603099999999995, + 0.68803099999999995, + 0.69003099999999995, + 0.69203099999999995, + 0.69403099999999995, + 0.69603000000000004, + 0.69803000000000004, + 0.70003000000000004, + 0.70203000000000004, + 0.70403000000000004, + 0.70602900000000002, + 0.70802900000000002, + 0.71002900000000002, + 0.71202900000000002, + 0.71402900000000002, + 0.716028, + 0.718028, + 0.720028, + 0.722028, + 0.724028, + 0.72602699999999998, + 0.72802699999999998, + 0.73002699999999998, + 0.73202699999999998, + 0.73402699999999999, + 0.73602599999999996, + 0.73802599999999996, + 0.74002599999999996, + 0.74202599999999996, + 0.74402599999999997, + 0.74602500000000005, + 0.74802500000000005, + 0.75002500000000005, + 0.75202500000000005, + 0.75402499999999995, + 0.75602400000000003, + 0.75802400000000003, + 0.76002400000000003, + 0.76202400000000003, + 0.76402400000000004, + 0.76602300000000001, + 0.76802300000000001, + 0.77002300000000001, + 0.77202300000000001, + 0.77402300000000002, + 0.77602199999999999, + 0.77802199999999999, + 0.78002199999999999, + 0.78202199999999999, + 0.784022, + 0.78602099999999997, + 0.78802099999999997, + 0.79002099999999997, + 0.79202099999999998, + 0.79402099999999998, + 0.79601999999999995, + 0.79801999999999995, + 0.80001999999999995, + 0.80201999999999996, + 0.80401999999999996, + 0.80601900000000004, + 0.80801900000000004, + 0.81001900000000004, + 0.81201900000000005, + 0.81401900000000005, + 0.81601800000000002, + 0.81801800000000002, + 0.82001800000000002, + 0.82201800000000003, + 0.82401800000000003, + 0.826017, + 0.828017, + 0.830017, + 0.83201700000000001, + 0.83401700000000001, + 0.83601599999999998, + 0.83801599999999998, + 0.84001599999999998, + 0.84201599999999999, + 0.84401599999999999, + 0.84601499999999996, + 0.84801499999999996, + 0.85001499999999997, + 0.85201499999999997, + 0.85401499999999997, + 0.85601400000000005, + 0.85801400000000005, + 0.86001399999999995, + 0.86201399999999995, + 0.86401399999999995, + 0.86601300000000003, + 0.86801300000000003, + 0.87001300000000004, + 0.87201300000000004, + 0.87401300000000004, + 0.87601200000000001, + 0.87801200000000001, + 0.88001200000000002, + 0.88201200000000002, + 0.88401200000000002, + 0.88601099999999999, + 0.88801099999999999, + 0.890011, + 0.892011, + 0.894011, + 0.89600999999999997, + 0.89800999999999997, + 0.90000999999999998, + 0.90200999999999998, + 0.90400999999999998, + 0.90600899999999995, + 0.90800899999999996, + 0.91000899999999996, + 0.91200899999999996, + 0.91400899999999996, + 0.91600800000000004, + 0.91800800000000005, + 0.92000800000000005, + 0.92200800000000005, + 0.92400800000000005, + 0.92600700000000002, + 0.92800700000000003, + 0.93000700000000003, + 0.93200700000000003, + 0.93400700000000003, + 0.936006, + 0.93800600000000001, + 0.94000600000000001, + 0.94200600000000001, + 0.94400600000000001, + 0.94600499999999998, + 0.94800499999999999, + 0.95000499999999999, + 0.95200499999999999, + 0.95400499999999999, + 0.95600399999999996, + 0.95800399999999997, + 0.96000399999999997, + 0.96200399999999997, + 0.96400399999999997, + 0.96600299999999995, + 0.96800299999999995, + 0.97000299999999995, + 0.97200299999999995, + 0.97400299999999995, + 0.97600200000000004, + 0.97800200000000004, + 0.98000200000000004, + 0.98200200000000004, + 0.98400200000000004, + 0.98600100000000002, + 0.98800100000000002, + 0.99000100000000002, + 0.99200100000000002, + 0.99400100000000002, + 0.996, + 0.998, + 1, + 1.1180000000000001, + 1.236, + 1.3540000000000001, + 1.472, + 1.5900000000000001, + 1.708, + 1.8260000000000001, + 1.944, + 2.0619999999999998, + 2.1800000000000002, + 2.298, + 2.4159999999999999, + 2.5339999999999998, + 2.6520000000000001, + 2.77, + 2.8879999999999999, + 3.0059999999999998, + 3.1240000000000001, + 3.242, + 3.3599999999999999, + 3.4780000000000002, + 3.5960000000000001, + 3.714, + 3.8319999999999999, + 3.9500000000000002, + 4.0679999999999996, + 4.1859999999999999, + 4.3040000000000003, + 4.4219999999999997, + 4.54, + 4.6580000000000004, + 4.7759999999999998, + 4.8940000000000001, + 5.0119999999999996, + 5.1299999999999999, + 5.2480000000000002, + 5.3659999999999997, + 5.484, + 5.6020000000000003, + 5.7199999999999998, + 5.8380000000000001, + 5.9560000000000004, + 6.0739999999999998, + 6.1920000000000002, + 6.3099999999999996, + 6.4279999999999999, + 6.5460000000000003, + 6.6639999999999997, + 6.782, + 6.9000000000000004, + 7.0179999999999998, + 7.1360000000000001, + 7.2539999999999996, + 7.3719999999999999, + 7.4900000000000002, + 7.6079999999999997, + 7.726, + 7.8440000000000003, + 7.9619999999999997, + 8.0800000000000001, + 8.1980000000000004, + 8.3160000000000007, + 8.4339999999999993, + 8.5519999999999996, + 8.6699999999999999, + 8.7880000000000003, + 8.9060000000000006, + 9.0239999999999991, + 9.1419999999999995, + 9.2599999999999998, + 9.3780000000000001, + 9.4960000000000004, + 9.6140000000000008, + 9.7319999999999993, + 9.8499999999999996, + 9.968, + 10.086, + 10.204000000000001, + 10.321999999999999, + 10.44, + 10.558, + 10.676, + 10.794, + 10.912000000000001, + 11.029999999999999, + 11.148, + 11.266, + 11.384, + 11.502000000000001, + 11.619999999999999, + 11.738, + 11.856, + 11.974, + 12.092000000000001, + 12.210000000000001, + 12.327999999999999, + 12.446, + 12.564, + 12.682, + 12.800000000000001, + 12.917999999999999, + 13.036, + 13.154, + 13.272, + 13.390000000000001, + 13.507999999999999, + 13.625999999999999, + 13.744, + 13.862, + 13.98, + 14.098000000000001, + 14.215999999999999, + 14.334, + 14.452, + 14.57, + 14.688000000000001, + 14.805999999999999, + 14.923999999999999, + 15.042, + 15.16, + 15.278, + 15.396000000000001, + 15.513999999999999, + 15.632, + 15.75, + 15.868, + 15.986000000000001, + 16.103999999999999, + 16.222000000000001, + 16.34, + 16.457999999999998, + 16.576000000000001, + 16.693999999999999, + 16.812000000000001, + 16.93, + 17.047999999999998, + 17.166, + 17.283999999999999, + 17.402000000000001, + 17.52, + 17.638000000000002, + 17.756, + 17.873999999999999, + 17.992000000000001, + 18.109999999999999, + 18.228000000000002, + 18.346, + 18.463999999999999, + 18.582000000000001, + 18.699999999999999, + 18.818000000000001, + 18.936, + 19.053999999999998, + 19.172000000000001, + 19.289999999999999, + 19.408000000000001, + 19.526, + 19.643999999999998, + 19.762, + 19.879999999999999, + 19.998000000000001, + 20.116, + 20.234000000000002, + 20.352, + 20.469999999999999, + 20.588000000000001, + 20.706, + 20.824000000000002, + 20.942, + 21.059999999999999, + 21.178000000000001, + 21.295999999999999, + 21.414000000000001, + 21.532, + 21.649999999999999, + 21.768000000000001, + 21.885999999999999, + 22.004000000000001, + 22.122, + 22.239999999999998, + 22.358000000000001, + 22.475999999999999, + 22.594000000000001, + 22.712, + 22.829999999999998, + 22.948, + 23.065999999999999, + 23.184000000000001, + 23.302, + 23.420000000000002, + 23.538, + 23.655999999999999, + 23.774000000000001, + 23.891999999999999, + 24.010000000000002, + 24.128, + 24.245999999999999, + 24.364000000000001, + 24.481999999999999, + 24.600000000000001, + 24.718, + 24.835999999999999, + 24.954000000000001, + 25.071999999999999, + 25.190000000000001, + 25.308, + 25.425999999999998, + 25.544, + 25.661999999999999, + 25.780000000000001, + 25.898, + 26.015999999999998, + 26.134, + 26.251999999999999, + 26.370000000000001, + 26.488, + 26.606000000000002, + 26.724, + 26.841999999999999, + 26.960000000000001, + 27.077999999999999, + 27.196000000000002, + 27.314, + 27.431999999999999, + 27.550000000000001, + 27.667999999999999, + 27.786000000000001, + 27.904, + 28.021999999999998, + 28.140000000000001, + 28.257999999999999, + 28.376000000000001, + 28.494, + 28.611999999999998, + 28.73, + 28.847999999999999, + 28.966000000000001, + 29.084, + 29.202000000000002, + 29.32, + 29.437999999999999, + 29.556000000000001, + 29.673999999999999, + 29.792000000000002, + 29.91, + 30.027999999999999, + 30.146000000000001, + 30.263999999999999, + 30.382000000000001, + 30.5, + 30.617999999999999, + 30.736000000000001, + 30.853999999999999, + 30.972000000000001, + 31.09, + 31.207999999999998, + 31.326000000000001, + 31.443999999999999, + 31.562000000000001, + 31.68, + 31.797999999999998, + 31.916, + 32.033999999999999, + 32.152000000000001, + 32.270000000000003, + 32.387999999999998, + 32.506, + 32.624000000000002, + 32.741999999999997, + 32.859999999999999, + 32.978000000000002, + 33.095999999999997, + 33.213999999999999, + 33.332000000000001, + 33.450000000000003, + 33.567999999999998, + 33.686, + 33.804000000000002, + 33.921999999999997, + 34.039999999999999, + 34.158000000000001, + 34.276000000000003, + 34.393999999999998, + 34.512, + 34.630000000000003, + 34.747999999999998, + 34.866, + 34.984000000000002, + 35.101999999999997, + 35.219999999999999, + 35.338000000000001, + 35.456000000000003, + 35.573999999999998, + 35.692, + 35.810000000000002, + 35.927999999999997, + 36.045999999999999, + 36.164000000000001, + 36.281999999999996, + 36.399999999999999, + 36.518000000000001, + 36.636000000000003, + 36.753999999999998, + 36.872, + 36.990000000000002, + 37.107999999999997, + 37.225999999999999, + 37.344000000000001, + 37.462000000000003, + 37.579999999999998, + 37.698, + 37.816000000000003, + 37.933999999999997, + 38.052, + 38.170000000000002, + 38.287999999999997, + 38.405999999999999, + 38.524000000000001, + 38.642000000000003, + 38.759999999999998, + 38.878, + 38.996000000000002, + 39.113999999999997, + 39.231999999999999, + 39.350000000000001, + 39.468000000000004, + 39.585999999999999, + 39.704000000000001, + 39.822000000000003, + 39.939999999999998, + 40.058, + 40.176000000000002, + 40.293999999999997, + 40.411999999999999, + 40.530000000000001, + 40.648000000000003, + 40.765999999999998, + 40.884, + 41.002000000000002, + 41.119999999999997, + 41.238, + 41.356000000000002, + 41.473999999999997, + 41.591999999999999, + 41.710000000000001, + 41.828000000000003, + 41.945999999999998, + 42.064, + 42.182000000000002, + 42.299999999999997, + 42.417999999999999, + 42.536000000000001, + 42.654000000000003, + 42.771999999999998, + 42.890000000000001, + 43.008000000000003, + 43.125999999999998, + 43.244, + 43.362000000000002, + 43.479999999999997, + 43.597999999999999, + 43.716000000000001, + 43.834000000000003, + 43.951999999999998, + 44.07, + 44.188000000000002, + 44.305999999999997, + 44.423999999999999, + 44.542000000000002, + 44.659999999999997, + 44.777999999999999, + 44.896000000000001, + 45.014000000000003, + 45.131999999999998, + 45.25, + 45.368000000000002, + 45.485999999999997, + 45.603999999999999, + 45.722000000000001, + 45.840000000000003, + 45.957999999999998, + 46.076000000000001, + 46.194000000000003, + 46.311999999999998, + 46.43, + 46.548000000000002, + 46.665999999999997, + 46.783999999999999, + 46.902000000000001, + 47.020000000000003, + 47.137999999999998, + 47.256, + 47.374000000000002, + 47.491999999999997, + 47.609999999999999, + 47.728000000000002, + 47.845999999999997, + 47.963999999999999, + 48.082000000000001, + 48.200000000000003, + 48.317999999999998, + 48.436, + 48.554000000000002, + 48.671999999999997, + 48.789999999999999, + 48.908000000000001, + 49.026000000000003, + 49.143999999999998, + 49.262, + 49.380000000000003, + 49.497999999999998, + 49.616, + 49.734000000000002, + 49.851999999999997, + 49.969999999999999, + 50.088000000000001, + 50.206000000000003, + 50.323999999999998, + 50.442, + 50.560000000000002, + 50.677999999999997, + 50.795999999999999, + 50.914000000000001, + 51.031999999999996, + 51.149999999999999, + 51.268000000000001, + 51.386000000000003, + 51.503999999999998, + 51.622, + 51.740000000000002, + 51.857999999999997, + 51.975999999999999, + 52.094000000000001, + 52.212000000000003, + 52.329999999999998, + 52.448, + 52.566000000000003, + 52.683999999999997, + 52.802, + 52.920000000000002, + 53.037999999999997, + 53.155999999999999, + 53.274000000000001, + 53.392000000000003, + 53.509999999999998, + 53.628, + 53.746000000000002, + 53.863999999999997, + 53.981999999999999, + 54.100000000000001, + 54.218000000000004, + 54.335999999999999, + 54.454000000000001, + 54.572000000000003, + 54.689999999999998, + 54.808, + 54.926000000000002, + 55.043999999999997, + 55.161999999999999, + 55.280000000000001, + 55.398000000000003, + 55.515999999999998, + 55.634, + 55.752000000000002, + 55.869999999999997, + 55.988, + 56.106000000000002, + 56.223999999999997, + 56.341999999999999, + 56.460000000000001, + 56.578000000000003, + 56.695999999999998, + 56.814, + 56.932000000000002, + 57.049999999999997, + 57.167999999999999, + 57.286000000000001, + 57.404000000000003, + 57.521999999999998, + 57.640000000000001, + 57.758000000000003, + 57.875999999999998, + 57.994, + 58.112000000000002, + 58.229999999999997, + 58.347999999999999, + 58.466000000000001, + 58.584000000000003, + 58.701999999999998, + 58.82, + 58.938000000000002, + 59.055999999999997, + 59.173999999999999, + 59.292000000000002, + 59.409999999999997, + 59.527999999999999, + 59.646000000000001, + 59.764000000000003, + 59.881999999999998, + 80.882000000000005, + 101.88200000000001, + 122.88200000000001, + 143.88200000000001, + 164.88200000000001, + 185.88200000000001, + 206.88200000000001, + 227.88200000000001, + 248.88200000000001, + 269.88200000000001, + 290.88200000000001, + 311.88200000000001, + 332.88200000000001, + 353.88200000000001, + 374.88200000000001, + 395.88200000000001, + 416.88200000000001, + 437.88200000000001, + 458.88200000000001, + 479.88200000000001, + 500.88200000000001, + 521.88199999999995, + 542.88199999999995, + 563.88199999999995, + 584.88199999999995, + 605.88199999999995, + 626.88199999999995, + 647.88199999999995, + 668.88199999999995, + 689.88199999999995, + 710.88199999999995, + 731.88199999999995, + 752.88199999999995, + 773.88199999999995, + 794.88199999999995, + 815.88199999999995, + 836.88199999999995, + 857.88199999999995, + 878.88199999999995, + 899.88199999999995, + 920.88199999999995, + 941.88199999999995, + 962.88199999999995, + 983.88199999999995, + 1004.88, + 1025.8800000000001, + 1046.8800000000001, + 1067.8800000000001, + 1088.8800000000001, + 1109.8800000000001, + 1130.8800000000001, + 1151.8800000000001, + 1172.8800000000001, + 1193.8800000000001, + 1214.8800000000001, + 1235.8800000000001, + 1256.8800000000001, + 1277.8800000000001, + 1298.8800000000001, + 1319.8800000000001, + 1340.8800000000001, + 1361.8800000000001, + 1382.8800000000001, + 1403.8800000000001, + 1424.8800000000001, + 1445.8800000000001, + 1466.8800000000001, + 1487.8800000000001, + 1508.8800000000001, + 1529.8800000000001, + 1550.8800000000001, + 1571.8800000000001, + 1592.8800000000001, + 1613.8800000000001, + 1634.8800000000001, + 1655.8800000000001, + 1676.8800000000001, + 1697.8800000000001, + 1718.8800000000001, + 1739.8800000000001, + 1760.8800000000001, + 1781.8800000000001, + 1802.8800000000001, + 1823.8800000000001, + 1844.8800000000001, + 1865.8800000000001, + 1886.8800000000001, + 1907.8800000000001, + 1928.8800000000001, + 1949.8800000000001, + 1970.8800000000001, + 1991.8800000000001, + 2012.8800000000001, + 2033.8800000000001, + 2054.8800000000001, + 2075.8800000000001, + 2096.8800000000001, + 2117.8800000000001, + 2138.8800000000001, + 2159.8800000000001, + 2180.8800000000001, + 2201.8800000000001, + 2222.8800000000001, + 2243.8800000000001, + 2264.8800000000001, + 2285.8800000000001, + 2306.8800000000001, + 2327.8800000000001, + 2348.8800000000001 + ] + }, + "name" : "ElectronRange", + "type" : "LinterpFunction" + }, + { + "data" : + { + "fiducials" : + [ + "PolyFiducial:fiducial_data_xy", + "PolyFiducial:fiducial_data_zx" + ], + "logic" : "and" + }, + "name" : "uboone_data_fid", + "type" : "CompositeFiducial" + }, + { + "data" : + { + "axis" : 2, + "slabs" : + [ + { + "corners" : + [ + [ + 30, + -1130 + ], + [ + 770, + -1130 + ], + [ + 2530, + -960 + ], + [ + 2530, + 990 + ], + [ + 970, + 1130 + ], + [ + 30, + 1140 + ] + ], + "max" : 10370, + "min" : 0 + } + ] + }, + "name" : "fiducial_data_xy", + "type" : "PolyFiducial" + }, + { + "data" : + { + "axis" : 1, + "slabs" : + [ + { + "corners" : + [ + [ + 40, + 30 + ], + [ + 40, + 1170 + ], + [ + 150, + 2530 + ], + [ + 10220, + 2530 + ], + [ + 10330, + 1170 + ], + [ + 10330, + 30 + ] + ], + "max" : 1150, + "min" : -1150 + } + ] + }, + "name" : "fiducial_data_zx", + "type" : "PolyFiducial" + }, + { + "data" : + { + "fiducials" : + [ + "PolyFiducial:fiducial_mc_xy", + "PolyFiducial:fiducial_mc_zx" + ], + "logic" : "and" + }, + "name" : "uboone_mc_fid", + "type" : "CompositeFiducial" + }, + { + "data" : + { + "axis" : 2, + "slabs" : + [ + { + "corners" : + [ + [ + 30, + -1130 + ], + [ + 310, + -1130 + ], + [ + 2530, + -950 + ], + [ + 2530, + 970 + ], + [ + 670, + 1130 + ], + [ + 30, + 1140 + ] + ], + "max" : 10370, + "min" : 0 + } + ] + }, + "name" : "fiducial_mc_xy", + "type" : "PolyFiducial" + }, + { + "data" : + { + "axis" : 1, + "slabs" : + [ + { + "corners" : + [ + [ + 40, + 30 + ], + [ + 40, + 470 + ], + [ + 180, + 2530 + ], + [ + 10190, + 2530 + ], + [ + 10330, + 370 + ], + [ + 10330, + 30 + ] + ], + "max" : 1150, + "min" : -1150 + } + ] + }, + "name" : "fiducial_mc_zx", + "type" : "PolyFiducial" + }, + { + "data" : + { + "enable_debug" : false + }, + "name" : "tagger", + "type" : "ClusteringTaggerFlagTransfer" + }, + { + "data" : + { + "array_name" : "isolated", + "grouping" : "live", + "pcarray_name" : "perblob" + }, + "name" : "recover_bundle", + "type" : "ClusteringRecoveringBundle" + }, + { + "data" : + { + "detector_volumes" : "DetectorVolumes" + }, + "name" : "", + "type" : "PCTransformSet" + }, + { + "data" : + { + "coords" : + [ + "x", + "y", + "z" + ], + "correction_name" : "T0Correction", + "pc_name" : "3d", + "pc_transforms" : "PCTransformSet" + }, + "name" : "", + "type" : "ClusteringSwitchScope" + }, + { + "data" : + { + "drift_speed" : 0.001101, + "extra" : + [ + ".*wire_index", + ".*charge.*", + "wpid" + ], + "strategy" : + { + "disable_mix_dead_cell" : false, + "name" : "charge_stepped" + }, + "time_offset" : -1594550.4087193462 + }, + "name" : "live_no_dead_mix", + "type" : "BlobSampler" + }, + { + "data" : + { + "anodes" : + [ + "AnodePlane:uboone" + ], + "detector_volumes" : "DetectorVolumes", + "pc_transforms" : "PCTransformSet", + "samplers" : + [ + { + "apa" : 0, + "face" : 0, + "name" : "BlobSampler:live_no_dead_mix" + } + ], + "verbose" : true + }, + "name" : "", + "type" : "ImproveCluster_2" + }, + { + "data" : + { + "detector_volumes" : "DetectorVolumes", + "graph" : "steiner", + "grouping" : "live", + "pc_transforms" : "PCTransformSet", + "retiler" : "ImproveCluster_2" + }, + "name" : "", + "type" : "CreateSteinerGraph" + }, + { + "data" : + { + "dead" : "dead", + "detector_volumes" : "DetectorVolumes", + "fiducial" : "CompositeFiducial:uboone_mc_fid", + "live" : "live", + "pc_transforms" : "PCTransformSet", + "target" : "live" + }, + "name" : "", + "type" : "MakeFiducialUtils" + }, + { + "data" : + { + "detector_volumes" : "DetectorVolumes", + "grouping" : "live", + "particle_dataset" : "ParticleDataSet:ParticleDataSet", + "pc_transforms" : "PCTransformSet", + "recombination_model" : "BoxRecombination:box_recomb", + "trackfitting_config_file" : "uboone_track_fitting.json" + }, + "name" : "", + "type" : "TaggerCheckNeutrino" + }, + { + "data" : + { + "anodes" : + [ + "AnodePlane:uboone" + ], + "dQdx_offset" : -1000, + "dQdx_scale" : 0.10000000000000001, + "detector_volumes" : "DetectorVolumes", + "eventNo" : 1, + "flag_skip_vertex" : false, + "grouping" : "live", + "output_filename" : "track_com_1_1.root", + "runNo" : 1, + "subRunNo" : 1 + }, + "name" : "", + "type" : "UbooneMagnifyTrackingVisitor" + }, + { + "data" : + { + "anodes" : + [ + "AnodePlane:uboone" + ], + "bee_points_sets" : + [ + { + "algorithm" : "regular", + "coords" : + [ + "x_t0cor", + "y", + "z" + ], + "detector" : "uboone", + "filter" : 1, + "individual" : false, + "name" : "regular", + "pcname" : "3d", + "visitor" : "CreateSteinerGraph" + }, + { + "algorithm" : "steiner", + "coords" : + [ + "x_t0cor", + "y", + "z" + ], + "detector" : "uboone", + "individual" : false, + "name" : "steiner", + "pcname" : "steiner_pc", + "visitor" : "CreateSteinerGraph" + }, + { + "algorithm" : "track_fit", + "coords" : + [ + "x", + "y", + "z" + ], + "detector" : "uboone", + "grouping" : "live", + "individual" : false, + "name" : "track_fit", + "pcname" : "3d", + "visitor" : "TaggerCheckNeutrino" + } + ], + "bee_zip" : "bee.zip", + "detector_volumes" : "DetectorVolumes", + "eventNo" : 1, + "initial_index" : 0, + "inpath" : "pointtrees/%d", + "outpath" : "pointtrees/%d", + "perf" : true, + "pipeline" : + [ + "ClusteringTaggerFlagTransfer:tagger", + "ClusteringRecoveringBundle:recover_bundle", + "ClusteringSwitchScope", + "CreateSteinerGraph", + "MakeFiducialUtils", + "TaggerCheckNeutrino", + "UbooneMagnifyTrackingVisitor" + ], + "runNo" : 1, + "save_deadarea" : true, + "subRunNo" : 1, + "use_config_rse" : true + }, + "name" : "", + "type" : "MultiAlgBlobClustering" + }, + { + "data" : + { + "datapath" : "pointtrees/%d/live" + }, + "name" : "", + "type" : "ClusterFlashDump" + }, + { + "data" : + { + "edges" : + [ + { + "head" : + { + "node" : "MultiAlgBlobClustering", + "port" : 0 + }, + "tail" : + { + "node" : "UbooneClusterSource:live", + "port" : 0 + } + }, + { + "head" : + { + "node" : "UbooneClusterSource:live", + "port" : 0 + }, + "tail" : + { + "node" : "BlobSetMerge:live", + "port" : 0 + } + }, + { + "head" : + { + "node" : "BlobSetMerge:live", + "port" : 0 + }, + "tail" : + { + "node" : "UbooneBlobSource:live-uvw", + "port" : 0 + } + }, + { + "head" : + { + "node" : "BlobSetMerge:live", + "port" : 1 + }, + "tail" : + { + "node" : "UbooneBlobSource:live-uv", + "port" : 0 + } + }, + { + "head" : + { + "node" : "BlobSetMerge:live", + "port" : 2 + }, + "tail" : + { + "node" : "UbooneBlobSource:live-vw", + "port" : 0 + } + }, + { + "head" : + { + "node" : "BlobSetMerge:live", + "port" : 3 + }, + "tail" : + { + "node" : "UbooneBlobSource:live-wu", + "port" : 0 + } + }, + { + "head" : + { + "node" : "ClusterFlashDump", + "port" : 0 + }, + "tail" : + { + "node" : "MultiAlgBlobClustering", + "port" : 0 + } + } + ] + }, + "type" : "Pgrapher" + } +] diff --git a/clus/test/doctest_init_first_segment.cxx b/clus/test/doctest_init_first_segment.cxx new file mode 100644 index 000000000..e6b2d0638 --- /dev/null +++ b/clus/test/doctest_init_first_segment.cxx @@ -0,0 +1,259 @@ +#include "WireCellClus/PatternDebugIO.h" +#include "WireCellClus/NeutrinoPatternBase.h" +#include "WireCellClus/PRGraph.h" +#include "WireCellClus/TrackFitting.h" +#include "WireCellClus/Facade.h" + +#include "WireCellIface/IDetectorVolumes.h" +#include "WireCellIface/IAnodePlane.h" +#include "WireCellClus/IPCTransform.h" + +#include "WireCellUtil/PluginManager.h" +#include "WireCellUtil/NamedFactory.h" +#include "WireCellUtil/Persist.h" +#include "WireCellUtil/Logging.h" +#include "WireCellUtil/doctest.h" + +#include + +using namespace WireCell; +using namespace WireCell::Clus; +using namespace WireCell::Clus::PR; + +// These are commands to save data and run tests ... +// #Generate dump data - Run your full pipeline with: +// WCT_DUMP_INIT_FIRST_SEGMENT=./tmp/test_data.json wire-cell -l stderr -A kind=both -A beezip=mabc_0.zip -A initial_index="0" -A initial_runNo="5384" -A initial_subRunNo="130" -A initial_eventNo="6501" -A infiles=rootfiles/nuselEval_5384_130_6501.root uboone-mabc.jsonnet +// # Run geometry-only test (no DV/PCTS needed): +// WCT_TEST_DUMP=./tmp/test_data.json wcdoctest-clus -tc="init_first_segment geometry only" +// # generate test configuration +// wcsonnet -o ./tmp/test_config.json uboone-mabc.jsonnet +// # Run full end-to-end test (needs WCT config for DV/PCTS): +// WCT_TEST_DUMP=./tmp/test_data.json WCT_TEST_CONFIG=./tmp/test_config.json wcdoctest-clus -tc="init_first_segment end-to-end" + + + + +/// Helper: configure WCT components from a JSON config array. +/// The config should be an array of objects, each with "type", optional "name", +/// and "data" keys (standard WCT configuration format). +/// Components that can't be found are silently skipped, so you can pass the +/// full evaluated jsonnet config without filtering. +static void configure_components(const Json::Value& configs) +{ + auto log = Log::logger("test"); + for (const auto& comp : configs) { + std::string type = comp["type"].asString(); + std::string name = comp.get("name", "").asString(); + std::string tn = type; + if (!name.empty()) tn += ":" + name; + + try { + auto icfg = Factory::lookup_tn(tn); + auto cfg = icfg->default_configuration(); + + // Overlay saved data onto defaults + if (comp.isMember("data")) { + for (const auto& key : comp["data"].getMemberNames()) { + cfg[key] = comp["data"][key]; + } + } + icfg->configure(cfg); + log->debug("Configured {}", tn); + } catch (const std::exception& e) { + log->debug("Skipping {}: {}", tn, e.what()); + } + } +} + +TEST_CASE("init_first_segment geometry only") +{ + // This test validates the geometry/selection logic without needing + // DetectorVolumes or TrackFitting. + // + // Usage: + // - Without env var: uses default data file in clus/test/data/ + // - With env var (for development): + // WCT_TEST_DUMP=/path/to/dump.json wcdoctest-clus -tc="init_first_segment geometry only" + + // Default path to test data (relative to build directory) + const char* default_dump = "../clus/test/data/init_first_segment_input.json"; + + // Use env var if set, otherwise fall back to default + const char* dump_path = std::getenv("WCT_TEST_DUMP"); + if (!dump_path) dump_path = default_dump; + + MESSAGE("Using dump file: ", dump_path); + + auto data = DebugIO::load_init_first_segment_inputs(dump_path); + + REQUIRE(data.cluster != nullptr); + REQUIRE(data.main_cluster != nullptr); + + // Verify steiner_pc was loaded + REQUIRE(data.cluster->has_pc("steiner_pc")); + const auto& spc = data.cluster->get_pc("steiner_pc"); + CHECK(spc.size_major() > 0); + MESSAGE("Steiner PC has ", spc.size_major(), " points"); + + // Verify steiner_graph was loaded + REQUIRE(data.cluster->has_graph("steiner_graph")); + const auto& graph = data.cluster->get_graph("steiner_graph"); + CHECK(boost::num_vertices(graph) > 0); + CHECK(boost::num_edges(graph) > 0); + MESSAGE("Steiner graph: ", boost::num_vertices(graph), " vertices, ", + boost::num_edges(graph), " edges"); + + // Use precomputed boundary indices (computing them requires the anode) + auto boundary_indices = data.boundary_steiner_indices; + REQUIRE(boundary_indices.first != boundary_indices.second); + MESSAGE("Boundary indices: ", boundary_indices.first, ", ", boundary_indices.second); + + // Test rough path computation + const auto& scope = data.cluster->get_default_scope(); + MESSAGE("Scope name: ", scope.name, " with coords: ", scope.coords.size(), ", array size:", scope.coords[0].size()); + const auto& x_coords = spc.get(scope.coords.at(0))->elements(); + const auto& y_coords = spc.get(scope.coords.at(1))->elements(); + const auto& z_coords = spc.get(scope.coords.at(2))->elements(); + + Facade::geo_point_t first_pt( + x_coords[boundary_indices.first], + y_coords[boundary_indices.first], + z_coords[boundary_indices.first]); + Facade::geo_point_t second_pt( + x_coords[boundary_indices.second], + y_coords[boundary_indices.second], + z_coords[boundary_indices.second]); + + MESSAGE("Boundary point 1: (", first_pt.x(), ", ", first_pt.y(), ", ", first_pt.z(), ")"); + MESSAGE("Boundary point 2: (", second_pt.x(), ", ", second_pt.y(), ", ", second_pt.z(), ")"); + + // Test KNN on main_cluster steiner PC + if (data.main_cluster->has_pc("steiner_pc")) { + auto knn1 = data.main_cluster->kd_steiner_knn(1, first_pt, "steiner_pc"); + auto knn2 = data.main_cluster->kd_steiner_knn(1, second_pt, "steiner_pc"); + CHECK(!knn1.empty()); + CHECK(!knn2.empty()); + MESSAGE("KNN distances: ", std::sqrt(knn1[0].second), ", ", std::sqrt(knn2[0].second)); + } + + // Test rough path + PatternAlgorithms pattern_algos; + auto path_points = pattern_algos.do_rough_path(*data.cluster, first_pt, second_pt); + CHECK(path_points.size() > 1); + MESSAGE("Rough path has ", path_points.size(), " points"); +} + + +TEST_CASE("init_first_segment end-to-end") +{ + // Full end-to-end test that calls init_first_segment with real data. + // Requires DetectorVolumes and PCTransforms to be configured. + // + // Usage: + // - Without env vars: uses default data files in clus/test/data/ + // - With env vars (for development): + // WCT_TEST_DUMP=/path/to/dump.json WCT_TEST_CONFIG=/path/to/config.json \ + // wcdoctest-clus -tc="init_first_segment end-to-end" + + // Default paths to test data (relative to build directory) + const char* default_dump = "../clus/test/data/init_first_segment_input.json"; + const char* default_config = "../clus/test/data/uboone-mabc_config.json"; + + // Use env vars if set, otherwise fall back to defaults + const char* dump_path = std::getenv("WCT_TEST_DUMP"); + const char* config_path = std::getenv("WCT_TEST_CONFIG"); + if (!dump_path) dump_path = default_dump; + if (!config_path) config_path = default_config; + + MESSAGE("Using dump file: ", dump_path); + MESSAGE("Using config file: ", config_path); + + // Load plugins + PluginManager& pm = PluginManager::instance(); + pm.add("WireCellClus"); + pm.add("WireCellAux"); + pm.add("WireCellGen"); + pm.add("WireCellSigProc"); + + // Load and apply WCT configuration + auto wcfg_full = Persist::load(config_path); + + // Extract component configs from wire-cell JSON format + // Format: [{"type":"wire-cell", "data":{...}}, ...] + // The first item is metadata, rest are component configs + Json::Value wcfg(Json::arrayValue); + if (wcfg_full.isArray() && !wcfg_full.empty() && + wcfg_full[0]["type"].asString() == "wire-cell") { + // Skip first item (metadata), rest are component configs + for (Json::ArrayIndex i = 1; i < wcfg_full.size(); ++i) { + wcfg.append(wcfg_full[i]); + } + } else { + // Assume it's already just the configs array + wcfg = wcfg_full; + } + + configure_components(wcfg); + + // Get configured components + auto dv = Factory::find_tn("DetectorVolumes"); + auto pcts = Factory::find_tn("PCTransformSet"); + REQUIRE(dv); + REQUIRE(pcts); + + // Collect anodes from the config (same way MultiAlgBlobClustering does) + std::vector anodes; + for (const auto& comp : wcfg) { + if (comp["type"].asString() == "AnodePlane") { + std::string tn = "AnodePlane"; + std::string name = comp.get("name", "").asString(); + if (!name.empty()) tn += ":" + name; + anodes.push_back(Factory::find_tn(tn)); + } + } + REQUIRE(!anodes.empty()); + MESSAGE("Found ", anodes.size(), " AnodePlane(s)"); + + // Load cluster data + auto data = DebugIO::load_init_first_segment_inputs(dump_path); + REQUIRE(data.cluster != nullptr); + + // Set anodes and detector volumes on the grouping (required by Blob/Cluster methods) + auto* grouping = data.grouping_node->value.facade(); + grouping->set_anodes(anodes); + grouping->set_detector_volumes(dv); + + // Create and configure TrackFitting + auto tf = std::make_shared(); + tf->set_detector_volume(dv); + tf->set_pc_transforms(pcts); + tf->set_parameters(data.trackfitting_params); + + // Run init_first_segment + PR::Graph pr_graph; + PatternAlgorithms pattern_algos; + auto seg = pattern_algos.init_first_segment( + pr_graph, *data.cluster, data.main_cluster, + *tf, dv, data.flag_back_search); + + // Validate results + if (seg) { + MESSAGE("init_first_segment succeeded"); + CHECK(boost::num_vertices(pr_graph) >= 2); + + // Check vertex positions + auto [vbegin, vend] = boost::vertices(pr_graph); + for (auto vit = vbegin; vit != vend; ++vit) { + auto vtx = pr_graph[*vit].vertex; + if (vtx) { + MESSAGE("Vertex fit point: (", + vtx->fit().point.x(), ", ", + vtx->fit().point.y(), ", ", + vtx->fit().point.z(), ")"); + MESSAGE(" dQ=", vtx->fit().dQ, " dx=", vtx->fit().dx); + } + } + } else { + WARN("init_first_segment returned nullptr (path too short or fitting failed)"); + } +} diff --git a/gen/src/RecombinationModels.cxx b/gen/src/RecombinationModels.cxx index 233150c4b..5da6135fe 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) diff --git a/root/apps/wire-cell-uboone-magnify-tracking-convert.cxx b/root/apps/wire-cell-uboone-magnify-tracking-convert.cxx new file mode 100644 index 000000000..445a793ae --- /dev/null +++ b/root/apps/wire-cell-uboone-magnify-tracking-convert.cxx @@ -0,0 +1,551 @@ + +#include "WireCellUtil/NFKDVec.h" +#include "WireCellUtil/Point.h" +#include "WireCellUtil/Units.h" + +#include +#include + +#include "TTree.h" +#include "TFile.h" +#include "TChain.h" +#include "TString.h" + +#include "TGraph.h" +#include "TGraph2D.h" +#include "TVector3.h" + +#include + +using namespace WireCell; + +// Helper: add a PointVector to an NFKDVec tree +void add_points(NFKDVec::Tree& tree, const PointVector& ps) { + if (ps.empty()) return; + std::vector> coords(3); + coords[0].reserve(ps.size()); + coords[1].reserve(ps.size()); + coords[2].reserve(ps.size()); + for (const auto& p : ps) { + coords[0].push_back(p.x()); + coords[1].push_back(p.y()); + coords[2].push_back(p.z()); + } + tree.append(coords); +} + +// Helper: find the closest point in the tree, returns (distance, closest_point) +std::pair get_closest_point(const NFKDVec::Tree& tree, const Point& p) { + auto results = tree.knn(1, p); // Point has .data() and .size()==3 + if (results.empty()) { + return {0, Point(0, 0, 0)}; + } + size_t idx = results[0].first; + double dist = std::sqrt(results[0].second); // L2Simple returns squared distance + Point closest = tree.point3d(idx); + return {dist, closest}; +} + +int main(int argc, char* argv[]) +{ + if (argc < 2){ + std::cerr << "usage: wire-cell-track-com -a[truth.root] -b[reco.root] -t[reco_treename] -n[truth_treename] -o[out.root] -f[1:MC,2:data]" << std::endl; + return 1; + } + + TString reco_filename = "tracking_0_0_0.root"; + TString reco_treename = "T_rec_charge"; + TString proj_treename = "T_proj_data"; + + TString truth_filename = "mcs-tracks.root"; + TString truth_treename = "T"; + TString out_filename = "track_com.root"; + + int file_type = 1; // 1 for MC and 2 for data ... + + + + for(Int_t i = 1; i != argc; i++){ + switch(argv[i][1]){ + case 'b': + reco_filename = &argv[i][2]; + break; + case 't': + reco_treename = &argv[i][2]; + break; + case 'a': + truth_filename = &argv[i][2]; + break; + case 'n': + truth_treename = &argv[i][2]; + break; + case 'o': + out_filename = &argv[i][2]; + break; + case 'f': + file_type = atoi(&argv[i][2]); + break; + } + } + + if (file_type==1){ + std::cout << truth_filename << " " << reco_filename << " " << truth_treename << " " << reco_treename << std::endl; + }else{ + std::cout << reco_filename << " " << reco_treename << std::endl; + } + + TFile *file1 = new TFile(reco_filename); + TTree *T_bad_ch = (TTree*)file1->Get("T_bad_ch"); + + Double_t dQdx_scale = 1.;//1.2; + Double_t dQdx_offset = 0; + TTree *Trun = (TTree*)file1->Get("Trun"); + if (Trun!=0){ + if (Trun->GetBranch("dQdx_scale")){ + Trun->SetBranchAddress("dQdx_scale",&dQdx_scale); + Trun->SetBranchAddress("dQdx_offset",&dQdx_offset); + Trun->GetEntry(0); + } + } + //std::cout << dQdx_scale << " " << dQdx_offset << std::endl; + + TFile *file = new TFile(out_filename,"RECREATE"); + if (T_bad_ch!=0){ + T_bad_ch->CloneTree()->Write(); + } + + std::vector > *vx = new std::vector >; + std::vector > *vy = new std::vector >; + std::vector > *vz = new std::vector >; + std::vector > *vQ = new std::vector >; + std::vector *vN = new std::vector; + + std::vector *x = new std::vector; + std::vector *y = new std::vector; + std::vector *z = new std::vector; + std::vector *Q = new std::vector; + Int_t N; + + if (file_type==1){ // MC data ... + TChain *T_true = new TChain(truth_treename,truth_treename); + Int_t nfiles = T_true->Add(truth_filename); + + if (nfiles > 0 && T_true->GetEntries() > 0) { + TTree *t2 = new TTree("T_true","T_true"); + T_true->SetBranchAddress("N",&N); + T_true->SetBranchAddress("x",&x); + T_true->SetBranchAddress("y",&y); + T_true->SetBranchAddress("z",&z); + T_true->SetBranchAddress("Q",&Q); + T_true->GetEntry(0); + vN->push_back(N); + vx->push_back(*x); + vy->push_back(*y); + vz->push_back(*z); + vQ->push_back(*Q); + + for (size_t i=0;i!=vx->at(0).size();i++){ + vx->at(0).at(i) = (vx->at(0).at(i)+0.6)/1.098*1.1009999-0.1101; + } + + t2->Branch("N",&vN); + t2->Branch("x",&vx); + t2->Branch("y",&vy); + t2->Branch("z",&vz); + t2->Branch("Q",&vQ); + t2->Fill(); + } else { + std::cerr << "Warning: Truth file " << truth_filename << " does not exist or is empty. Skipping truth tree." << std::endl; + } + delete T_true; + } + + + + // std::cout << N << std::endl; + + // TGraph *g1_xy = new TGraph(); + // TGraph *g1_xz = new TGraph(); + // TGraph *g1_yz = new TGraph(); + + // TGraph2D *g1 = new TGraph2D(); + + NFKDVec::Tree pcloud(3), pcloud1(3); + + PointVector ps; + + if (file_type==1 && !x->empty()){ // MC ... + for (size_t i=0;i!=x->size();i++){ + // g1_xy->SetPoint(i,x->at(i),y->at(i)); + // g1_xz->SetPoint(i,x->at(i),z->at(i)); + // g1_yz->SetPoint(i,y->at(i),z->at(i)); + + x->at(i) = (x->at(i)+0.6)/1.098*1.1009999-0.1101; + // x1 = (x1+0.1101)/1.1009999*1.098-0.6;//+ 4*0.1101; + + Point p(x->at(i)*units::cm,y->at(i)*units::cm,z->at(i)*units::cm); + ps.push_back(p); + //g1->SetPoint(i,x->at(i),y->at(i),z->at(i)); + } + add_points(pcloud, ps); + } + + ps.clear(); + + + // g1_xy->SetLineColor(1); + // g1_xz->SetLineColor(1); + // g1_yz->SetLineColor(1); + + // g1_xy->SetLineWidth(2); + // g1_xz->SetLineWidth(2); + // g1_yz->SetLineWidth(2); + + // g1->SetLineColor(1); + // g1->SetLineWidth(2); + + + + TChain *T_rec = new TChain(reco_treename,reco_treename); + TChain *T_proj_data = new TChain(proj_treename,proj_treename); + TChain *T_proj = new TChain("T_proj","T_proj"); + + T_rec->Add(reco_filename); + T_proj_data->Add(reco_filename); + T_proj->Add(reco_filename); + Double_t x1,y1,z1; + Double_t dQ1,dx1,ndf; + Double_t pu, pv, pw, pt; + + T_rec->SetBranchAddress("x",&x1); + T_rec->SetBranchAddress("y",&y1); + T_rec->SetBranchAddress("z",&z1); + T_rec->SetBranchAddress("q",&dQ1); + T_rec->SetBranchAddress("nq",&dx1); + T_rec->SetBranchAddress("ndf",&ndf); + T_rec->SetBranchAddress("pu",&pu); + T_rec->SetBranchAddress("pv",&pv); + T_rec->SetBranchAddress("pw",&pw); + T_rec->SetBranchAddress("pt",&pt); + + Double_t reduced_chi2; + if (T_rec->GetBranch("reduced_chi2")){ + T_rec->SetBranchAddress("reduced_chi2",&reduced_chi2); + } + Int_t flag_vertex; + Int_t sub_cluster_id; + if (T_rec->GetBranch("flag_vertex")){ + T_rec->SetBranchAddress("flag_vertex",&flag_vertex); + T_rec->SetBranchAddress("sub_cluster_id",&sub_cluster_id); + } + + + TTree *t1 = new TTree("T_rec","T_rec"); + t1->SetDirectory(file); + std::vector *max_dis = new std::vector; // maximum distance along the track + std::vector *beg_dis = new std::vector; // distance at the beginning ... + std::vector *end_dis = new std::vector; // distance at the end ... + std::vector *total_dis2 = new std::vector; // total distance^2 + std::vector *total_L = new std::vector; + std::vector *Npoints = new std::vector; + std::vector *total_dtheta = new std::vector; + std::vector *max_dtheta = new std::vector; + if (file_type==1){ + t1->Branch("stat_max_dis",&max_dis); + t1->Branch("stat_beg_dis",&beg_dis); + t1->Branch("stat_end_dis",&end_dis); + t1->Branch("stat_total_dis2",&total_dis2); + t1->Branch("stat_total_dtheta",&total_dtheta); + t1->Branch("stat_max_dtheta",&max_dtheta); + } + t1->Branch("stat_total_L",&total_L); + t1->Branch("stat_N",&Npoints); + + + std::vector > *x2 = new std::vector >; + std::vector > *y2 = new std::vector >; + std::vector > *z2 = new std::vector >; + std::vector > *dQ_rec = new std::vector >; + std::vector > *dQ_tru = new std::vector >; + std::vector > *dx = new std::vector >; + std::vector *cluster_id = new std::vector; + std::vector > *rec_pu = new std::vector >; + std::vector > *rec_pv = new std::vector >; + std::vector > *rec_pw = new std::vector >; + std::vector > *rec_pt = new std::vector >; + + std::vector > *x2_pair = new std::vector >; + std::vector > *y2_pair = new std::vector >; + std::vector > *z2_pair = new std::vector >; + + std::vector > *dis = new std::vector >; + std::vector > *L = new std::vector >; + std::vector > *dtheta = new std::vector >; + + std::vector > *Vreduced_chi2 = new std::vector > ; + std::vector > *Vflag_vertex = new std::vector >; + std::vector > *Vsub_cluster_id = new std::vector >; + + + + t1->Branch("rec_x",&x2); + t1->Branch("rec_y",&y2); + t1->Branch("rec_z",&z2); + t1->Branch("rec_dQ",&dQ_rec); + t1->Branch("rec_dx",&dx); + t1->Branch("rec_L",&L); + t1->Branch("rec_cluster_id",&cluster_id); + t1->Branch("rec_u",&rec_pu); + t1->Branch("rec_v",&rec_pv); + t1->Branch("rec_w",&rec_pw); + t1->Branch("rec_t",&rec_pt); + if (T_rec->GetBranch("reduced_chi2")){ + t1->Branch("reduced_chi2",&Vreduced_chi2); + } + if (T_rec->GetBranch("flag_vertex")){ + t1->Branch("flag_vertex",&Vflag_vertex); + t1->Branch("sub_cluster_id",&Vsub_cluster_id); + } + + if (file_type==1){ + t1->Branch("true_dQ",&dQ_tru); + t1->Branch("true_x",&x2_pair); + t1->Branch("true_y",&y2_pair); + t1->Branch("true_z",&z2_pair); + + t1->Branch("com_dis",&dis); + t1->Branch("com_dtheta",&dtheta); + } + + double prev_x1, prev_y1, prev_z1; + int prev_cluster_id = -1; + + // Npoints = T_rec->GetEntries(); + + std::map, std::pair > map_point_index; + + // std::cout << T_rec->GetEntries() << std::endl; + + for (int i=0;i!=T_rec->GetEntries();i++){ + T_rec->GetEntry(i); + if (std::round(ndf)!=prev_cluster_id){ + Npoints->push_back(0); + total_L->push_back(0); + + std::vector temp_dQ_tru; + dQ_tru->push_back(temp_dQ_tru); + std::vector temp_dis; + dis->push_back(temp_dis); + std::vector temp_x2_pair; + x2_pair->push_back(temp_x2_pair); + std::vector temp_y2_pair; + y2_pair->push_back(temp_y2_pair); + std::vector temp_z2_pair; + z2_pair->push_back(temp_z2_pair); + + std::vector temp_x2; + x2->push_back(temp_x2); + std::vector temp_y2; + y2->push_back(temp_y2); + std::vector temp_z2; + z2->push_back(temp_z2); + std::vector temp_rec_pu; + rec_pu->push_back(temp_rec_pu); + std::vector temp_rec_pv; + rec_pv->push_back(temp_rec_pv); + std::vector temp_rec_pw; + rec_pw->push_back(temp_rec_pw); + std::vector temp_rec_pt; + rec_pt->push_back(temp_rec_pt); + cluster_id->push_back(std::round(ndf)); + std::vector temp_dQ_rec; + dQ_rec->push_back(temp_dQ_rec); + std::vector temp_dx; + dx->push_back(temp_dx); + std::vector temp_L; + L->push_back(temp_L); + + if (T_rec->GetBranch("reduced_chi2")){ + std::vector temp_reduced_chi2; + Vreduced_chi2->push_back(temp_reduced_chi2); + } + if (T_rec->GetBranch("flag_vertex")){ + std::vector temp_flag_vertex; + std::vector temp_sub_cluster_id; + Vflag_vertex->push_back(temp_flag_vertex); + Vsub_cluster_id->push_back(temp_sub_cluster_id); + } + + max_dis->push_back(0); + total_dis2->push_back(0); + } + Npoints->back()++; + + if (Npoints->back()!=1){ + total_L->back() += sqrt(pow(x1-prev_x1,2)+pow(y1-prev_y1,2)+pow(z1-prev_z1,2)); + } + + // binning effect 1 us later from the binned slice effect, rebinned 4 ... + // speed of imaging 1.101 mm / us + // speed of simulation 1.098 mm/us + // there is a potential 1 us offset at SP + // -0.6 cm is the distance difference between Y and U planes + // x1 = (x1+0.1101)/1.1009999*1.098-0.6;//+ 4*0.1101; + Point p(x1*units::cm, y1*units::cm, z1*units::cm); + ps.push_back(p); + + + + if (file_type==1){ + std::pair point_pair = get_closest_point(pcloud, p); + map_point_index[std::make_tuple(p.x()/(0.01*units::mm),p.y()/(0.01*units::mm),p.z()/(0.01*units::mm))] = std::make_pair(x2->size()-1,x2->back().size()); + dQ_tru->back().push_back(0); + dis->back().push_back(point_pair.first/units::cm); + + + x2_pair->back().push_back(point_pair.second.x()/units::cm); + y2_pair->back().push_back(point_pair.second.y()/units::cm); + z2_pair->back().push_back(point_pair.second.z()/units::cm); + + if (max_dis->back() < point_pair.first/units::cm) + max_dis->back() = point_pair.first/units::cm; + total_dis2->back() += pow(point_pair.first/units::cm,2); + + if (Npoints->back()==1){ + double dis1 = pow(x1-x->front(),2) + pow(y1-y->front(),2) + pow(z1-z->front(),2); + double dis2 = pow(x1-x->back(),2) + pow(y1-y->back(),2) + pow(z1-z->back(),2); + + // //std::cout << sqrt(dis1)/units::cm << " " << sqrt(dis2)/units::cm << std::endl; + if (dis1 < dis2){ + beg_dis->push_back(sqrt(dis1)); + }else{ + beg_dis->push_back(sqrt(dis2)); + } + end_dis->push_back(0); + } + // if (i==T_rec->GetEntries()-1) + { + double dis1 = pow(x1-x->front(),2) + pow(y1-y->front(),2) + pow(z1-z->front(),2); + double dis2 = pow(x1-x->back(),2) + pow(y1-y->back(),2) + pow(z1-z->back(),2); + if (dis1 < dis2){ + end_dis->back() = sqrt(dis1); + }else{ + end_dis->back() = sqrt(dis2); + } + } + } + + x2->back().push_back(x1); + y2->back().push_back(y1); + z2->back().push_back(z1); + rec_pu->back().push_back(pu); + rec_pv->back().push_back(pv); + rec_pw->back().push_back(pw); + rec_pt->back().push_back(pt); + dQ_rec->back().push_back((dQ1-dQdx_offset)/dQdx_scale); // hack to match the color scale + dx->back().push_back(dx1); + L->back().push_back(total_L->back()); + + if (T_rec->GetBranch("reduced_chi2")){ + Vreduced_chi2->back().push_back(reduced_chi2); + } + if (T_rec->GetBranch("flag_vertex")){ + Vflag_vertex->back().push_back(flag_vertex); + Vsub_cluster_id->back().push_back(sub_cluster_id); + } + + + prev_x1 = x1; + prev_y1 = y1; + prev_z1 = z1; + prev_cluster_id = std::round(ndf); + + //std::cout << prev_cluster_id << std::endl; + } // loop over i ... + + + add_points(pcloud1, ps); + + + + + //std::cout << "haha " << std::endl; + if (file_type==1 && !x->empty()){ + for (size_t i=0;i!=x->size();i++){ + Point p(x->at(i)*units::cm,y->at(i)*units::cm,z->at(i)*units::cm); + std::pair point_pair = get_closest_point(pcloud1, p); + int index = map_point_index[std::make_tuple(int(point_pair.second.x()/(0.01*units::mm)) + ,int(point_pair.second.y()/(0.01*units::mm)),int(point_pair.second.z()/(0.01*units::mm)))].second; + int index1 = map_point_index[std::make_tuple(int(point_pair.second.x()/(0.01*units::mm)) + ,int(point_pair.second.y()/(0.01*units::mm)),int(point_pair.second.z()/(0.01*units::mm)))].first; + + // std::cout << index << " " << dQ_tru->back().size() << " " << dQ_tru->front().size() << std::endl; + if (index at(index1).size()) + dQ_tru->at(index1).at(index) += Q->at(i); + + // if (i==0 || i==x->size()-1) + // std::cout << p << " " << sqrt(pow(p.x-point_pair.second.x,2)+pow(p.y-point_pair.second.y,2)+pow(p.z-point_pair.second.z,2))/units::cm << std::endl; + // g1->SetPoint(i,x->at(i),y->at(i),z->at(i)); + } + + // std::cout << x2->back().size() << std::endl; + + for (size_t k=0;k!=x2->size();k++){ + if (x2->at(k).size()>1){ + std::vector temp_dtheta; + dtheta->push_back(temp_dtheta); + max_dtheta->push_back(0); + total_dtheta->push_back(0); + + for (size_t i=0;i!=x2->at(k).size();i++){ + if (i==0){ + TVector3 dir1(x2->at(k).at(1)-x2->at(k).at(0), + y2->at(k).at(1)-y2->at(k).at(0), + z2->at(k).at(1)-z2->at(k).at(0)); + TVector3 dir2(x2_pair->at(k).at(1) - x2_pair->at(k).at(0), + y2_pair->at(k).at(1) - y2_pair->at(k).at(0), + z2_pair->at(k).at(1) - z2_pair->at(k).at(0)); + dtheta->at(k).push_back(dir1.Angle(dir2)); + }else if(i==x2->at(k).size()-1){ + TVector3 dir1(x2->at(k).at(x2->at(k).size()-1) - x2->at(k).at(x2->at(k).size()-2), + y2->at(k).at(x2->at(k).size()-1) - y2->at(k).at(x2->at(k).size()-2), + z2->at(k).at(x2->at(k).size()-1) - z2->at(k).at(x2->at(k).size()-2)); + TVector3 dir2(x2_pair->at(k).at(x2->at(k).size()-1) - x2_pair->at(k).at(x2->at(k).size()-2), + y2_pair->at(k).at(x2->at(k).size()-1) - y2_pair->at(k).at(x2->at(k).size()-2), + z2_pair->at(k).at(x2->at(k).size()-1) - z2_pair->at(k).at(x2->at(k).size()-2)); + dtheta->at(k).push_back(dir1.Angle(dir2)); + }else{ + TVector3 dir1(x2->at(k).at(i+1)-x2->at(k).at(i), + y2->at(k).at(i+1)-y2->at(k).at(i), + z2->at(k).at(i+1)-z2->at(k).at(i)); + TVector3 dir2(x2_pair->at(k).at(i+1) - x2_pair->at(k).at(i), + y2_pair->at(k).at(i+1) - y2_pair->at(k).at(i), + z2_pair->at(k).at(i+1) - z2_pair->at(k).at(i)); + + TVector3 dir3(x2->at(k).at(i-1)-x2->at(k).at(i), + y2->at(k).at(i-1)-y2->at(k).at(i), + z2->at(k).at(i-1)-z2->at(k).at(i)); + TVector3 dir4(x2_pair->at(k).at(i-1) - x2_pair->at(k).at(i), + y2_pair->at(k).at(i-1) - y2_pair->at(k).at(i), + z2_pair->at(k).at(i-1) - z2_pair->at(k).at(i)); + dtheta->at(k).push_back((dir1.Angle(dir2)+dir3.Angle(dir4))/2.); + } + if (dtheta->at(k).back() > max_dtheta->at(k)) + max_dtheta->at(k) = dtheta->at(k).back(); + total_dtheta->at(k) += dtheta->at(k).back(); + } + }else{ + dtheta->at(k).push_back(0); + } + } + } + t1->Fill(); + + T_proj_data->CloneTree(-1,"fast"); + T_proj->CloneTree(-1,"fast"); + + file->Write(); + file->Close(); + + +} diff --git a/root/inc/WireCellRoot/UbooneMagnifyTrackingVisitor.h b/root/inc/WireCellRoot/UbooneMagnifyTrackingVisitor.h new file mode 100644 index 000000000..8abac8b3e --- /dev/null +++ b/root/inc/WireCellRoot/UbooneMagnifyTrackingVisitor.h @@ -0,0 +1,75 @@ +/** Visitor that writes tracking data to a ROOT file. + * + * This runs as an IEnsembleVisitor inside the MABC pipeline, + * before tensor serialization, so it can access TrackFitting directly. + * Writes T_bad_ch, T_proj_data, and T_proj trees. + */ + +#ifndef WIRECELLROOT_UBOONEMAGNIFYTRACKINGVISITOR +#define WIRECELLROOT_UBOONEMAGNIFYTRACKINGVISITOR + +#include "WireCellClus/IEnsembleVisitor.h" +#include "WireCellIface/IConfigurable.h" +#include "WireCellIface/IAnodePlane.h" +#include "WireCellIface/IDetectorVolumes.h" +#include "WireCellUtil/Logging.h" + +class TFile; + +namespace WireCell { + namespace Root { + + // Structure to hold reconstruction point data + struct WCPointTree { + double reco_x{0}; + double reco_y{0}; + double reco_z{0}; + double reco_dQ{0}; + double reco_dx{0}; + double reco_chi2{0}; + double reco_ndf{0}; + double reco_pu{0}; + double reco_pv{0}; + double reco_pw{0}; + double reco_pt{0}; + double reco_reduced_chi2{0}; + int reco_flag_vertex{0}; + int reco_flag_track_shower{0}; + double reco_rr{0}; + int reco_mother_cluster_id{0}; + int reco_cluster_id{0}; + int reco_proto_cluster_id{0}; + int reco_particle_id{0}; + }; + + class UbooneMagnifyTrackingVisitor : public IConfigurable, public Clus::IEnsembleVisitor { + public: + UbooneMagnifyTrackingVisitor(); + virtual ~UbooneMagnifyTrackingVisitor(); + + virtual void configure(const WireCell::Configuration& config); + virtual Configuration default_configuration() const; + virtual void visit(Clus::Facade::Ensemble& ensemble) const; + + private: + Log::logptr_t log; + std::string m_output_filename; + std::string m_grouping_name{"live"}; + int m_runNo{0}; + int m_subRunNo{0}; + int m_eventNo{0}; + std::vector m_anodes; + IDetectorVolumes::pointer m_dv; + double m_dQdx_scale{0.1}; + double m_dQdx_offset{-1000}; + bool m_flag_skip_vertex{false}; + + void write_bad_channels(TFile* output_tf, Clus::Facade::Grouping& grouping) const; + void write_proj_data(TFile* output_tf, Clus::Facade::Grouping& grouping) const; + void write_t_rec_data(TFile* output_tf, Clus::Facade::Grouping& grouping) const; + void write_trun(TFile* output_tf) const; + }; + } // namespace Root +} // namespace WireCell + +#endif diff --git a/root/src/UbooneMagnifyTrackingVisitor.cxx b/root/src/UbooneMagnifyTrackingVisitor.cxx new file mode 100644 index 000000000..87ba08f0d --- /dev/null +++ b/root/src/UbooneMagnifyTrackingVisitor.cxx @@ -0,0 +1,477 @@ +#include "WireCellRoot/UbooneMagnifyTrackingVisitor.h" + +#include "TFile.h" +#include "TTree.h" + +#include "WireCellUtil/NamedFactory.h" +#include "WireCellUtil/Units.h" +#include "WireCellClus/TrackFitting.h" +#include "WireCellClus/Facade_Grouping.h" +#include "WireCellClus/ClusteringFuncs.h" +#include "WireCellClus/PRGraph.h" +#include "WireCellClus/PRVertex.h" +#include "WireCellClus/PRSegment.h" + +#include + +WIRECELL_FACTORY(UbooneMagnifyTrackingVisitor, WireCell::Root::UbooneMagnifyTrackingVisitor, + WireCell::IConfigurable, WireCell::Clus::IEnsembleVisitor) + +using namespace WireCell; +using namespace WireCell::Clus; + +Root::UbooneMagnifyTrackingVisitor::UbooneMagnifyTrackingVisitor() + : log(Log::logger("tracking")) +{ +} + +Root::UbooneMagnifyTrackingVisitor::~UbooneMagnifyTrackingVisitor() {} + +void Root::UbooneMagnifyTrackingVisitor::configure(const WireCell::Configuration& cfg) +{ + m_output_filename = get(cfg, "output_filename", "tracking_proj.root"); + m_grouping_name = get(cfg, "grouping", "live"); + m_runNo = get(cfg, "runNo", 0); + m_subRunNo = get(cfg, "subRunNo", 0); + m_eventNo = get(cfg, "eventNo", 0); + m_dQdx_scale = get(cfg, "dQdx_scale", 0.1); + m_dQdx_offset = get(cfg, "dQdx_offset", -1000); + m_flag_skip_vertex = get(cfg, "flag_skip_vertex", false); + + auto anode_tns = cfg["anodes"]; + for (auto anode_tn : anode_tns) { + auto anode = Factory::find_tn(anode_tn.asString()); + m_anodes.push_back(anode); + } + + m_dv = Factory::find_tn(cfg["detector_volumes"].asString()); +} + +WireCell::Configuration Root::UbooneMagnifyTrackingVisitor::default_configuration() const +{ + Configuration cfg; + cfg["output_filename"] = "tracking_proj.root"; + cfg["grouping"] = "live"; + cfg["anodes"] = Json::arrayValue; + cfg["detector_volumes"] = ""; + cfg["runNo"] = 0; + cfg["subRunNo"] = 0; + cfg["eventNo"] = 0; + cfg["dQdx_scale"] = 0.1; + cfg["dQdx_offset"] = -1000; + cfg["flag_skip_vertex"] = false; + return cfg; +} + +void Root::UbooneMagnifyTrackingVisitor::visit(Clus::Facade::Ensemble& ensemble) const +{ + auto groupings = ensemble.with_name(m_grouping_name); + if (groupings.empty()) { + log->debug("UbooneMagnifyTrackingVisitor: no grouping '{}'", m_grouping_name); + return; + } + + auto& grouping = *groupings.at(0); + + // Set anodes and detector volumes on the grouping + grouping.set_anodes(m_anodes); + grouping.set_detector_volumes(m_dv); + + // Open ROOT file + TFile* output_tf = TFile::Open(m_output_filename.c_str(), "RECREATE"); + if (!output_tf || output_tf->IsZombie()) { + log->error("UbooneMagnifyTrackingVisitor: cannot open {}", m_output_filename); + return; + } + + write_bad_channels(output_tf, grouping); + write_trun(output_tf); + write_proj_data(output_tf, grouping); + write_t_rec_data(output_tf, grouping); + + // Empty T_proj tree for now + TTree* tree_proj = new TTree("T_proj", "T_proj"); + tree_proj->SetDirectory(output_tf); + tree_proj->Fill(); + + output_tf->Write(); + output_tf->Close(); + delete output_tf; + + log->debug("UbooneMagnifyTrackingVisitor: wrote {}", m_output_filename); +} + +void Root::UbooneMagnifyTrackingVisitor::write_bad_channels(TFile* output_tf, Clus::Facade::Grouping& grouping) const +{ + TTree* tree = new TTree("T_bad_ch", "T_bad_ch"); + tree->SetDirectory(output_tf); + + int chid = 0; + int plane = 0; + int start_time = 0; + int end_time = 0; + int runNo = m_runNo; + int subRunNo = m_subRunNo; + int eventNo = m_eventNo; + + tree->Branch("chid", &chid, "chid/I"); + tree->Branch("plane", &plane, "plane/I"); + tree->Branch("start_time", &start_time, "start_time/I"); + tree->Branch("end_time", &end_time, "end_time/I"); + tree->Branch("runNo", &runNo, "runNo/I"); + tree->Branch("subRunNo", &subRunNo, "subRunNo/I"); + tree->Branch("eventNo", &eventNo, "eventNo/I"); + + auto wpids = grouping.wpids(); + std::set> apa_face_set; + for (const auto& wpid : wpids) { + apa_face_set.insert({wpid.apa(), wpid.face()}); + } + + for (const auto& [apa, face] : apa_face_set) { + for (int pind = 0; pind < 3; ++pind) { + try { + auto dead_chs = grouping.get_all_dead_chs(apa, face, pind); + plane = pind; + for (const auto& [ch, time_range] : dead_chs) { + chid = ch; + start_time = time_range.first; + end_time = time_range.second; + tree->Fill(); + } + } + catch (const std::exception& e) { + log->warn("UbooneMagnifyTrackingVisitor: failed to get dead channels for APA={}, face={}, plane={}: {}", + apa, face, pind, e.what()); + } + } + } + + log->debug("UbooneMagnifyTrackingVisitor: wrote {} entries to T_bad_ch", tree->GetEntries()); +} + +void Root::UbooneMagnifyTrackingVisitor::write_trun(TFile* output_tf) const +{ + TTree* tree = new TTree("Trun", "Trun"); + tree->SetDirectory(output_tf); + + int eventNo = m_eventNo; + int runNo = m_runNo; + int subRunNo = m_subRunNo; + double dQdx_scale = m_dQdx_scale; + double dQdx_offset = m_dQdx_offset; + + tree->Branch("eventNo", &eventNo, "eventNo/I"); + tree->Branch("runNo", &runNo, "runNo/I"); + tree->Branch("subRunNo", &subRunNo, "subRunNo/I"); + tree->Branch("dQdx_scale", &dQdx_scale, "dQdx_scale/D"); + tree->Branch("dQdx_offset", &dQdx_offset, "dQdx_offset/D"); + + tree->Fill(); + + log->debug("UbooneMagnifyTrackingVisitor: wrote Trun with dQdx_scale={}, dQdx_offset={}", dQdx_scale, dQdx_offset); +} + +void Root::UbooneMagnifyTrackingVisitor::write_proj_data(TFile* output_tf, Clus::Facade::Grouping& grouping) const +{ + auto tf = grouping.get_track_fitting(); + if (!tf) { + log->warn("UbooneMagnifyTrackingVisitor: no TrackFitting in grouping"); + return; + } + + const auto& fitted = tf->get_fitted_charge_2d(); + if (fitted.empty()) { + log->warn("UbooneMagnifyTrackingVisitor: fitted_charge_2d is empty"); + return; + } + + // Get ticks-per-slice map for time_slice conversion + auto nticks_map = grouping.get_nticks_per_slice(); + + // Reorganize fitted charge data by cluster_id + std::map> cluster_channels; + std::map> cluster_time_slices; + std::map> cluster_charges; + std::map> cluster_charge_errs; + std::map> cluster_charge_preds; + + for (const auto& [afp, wt_map] : fitted) { + int apa = std::get<0>(afp); + int face = std::get<1>(afp); + int plane_idx = std::get<2>(afp); + // uBooNE channel convention: U=wire, V=2400+wire, W=4800+wire + int ch_offset = (plane_idx == 1) ? 2400 : (plane_idx == 2) ? 4800 : 0; + + int nticks_per_slice = nticks_map.at(apa).at(face); + + for (const auto& [wt, fc] : wt_map) { + int wire = wt.first; + int time = wt.second / nticks_per_slice; + int channel = ch_offset + wire; + + for (auto* cl : fc.clusters) { + int cid = cl->get_cluster_id(); + cluster_channels[cid].push_back(channel); + cluster_time_slices[cid].push_back(time); + cluster_charges[cid].push_back(static_cast(fc.charge)); + cluster_charge_errs[cid].push_back(static_cast(fc.charge_err)); + cluster_charge_preds[cid].push_back(static_cast(fc.pred_charge)); + } + } + } + + // Build vectors in cluster_id order + std::vector v_cluster_id; + std::vector> v_channel; + std::vector> v_time_slice; + std::vector> v_charge; + std::vector> v_charge_err; + std::vector> v_charge_pred; + + for (const auto& [cid, chs] : cluster_channels) { + v_cluster_id.push_back(cid); + v_channel.push_back(chs); + v_time_slice.push_back(cluster_time_slices[cid]); + v_charge.push_back(cluster_charges[cid]); + v_charge_err.push_back(cluster_charge_errs[cid]); + v_charge_pred.push_back(cluster_charge_preds[cid]); + } + + TTree* tree = new TTree("T_proj_data", "T_proj_data"); + tree->SetDirectory(output_tf); + tree->Branch("cluster_id", &v_cluster_id); + tree->Branch("channel", &v_channel); + tree->Branch("time_slice", &v_time_slice); + tree->Branch("charge", &v_charge); + tree->Branch("charge_err", &v_charge_err); + tree->Branch("charge_pred", &v_charge_pred); + tree->Fill(); + + log->debug("UbooneMagnifyTrackingVisitor: wrote T_proj_data with {} clusters", v_cluster_id.size()); +} + +void Root::UbooneMagnifyTrackingVisitor::write_t_rec_data(TFile* output_tf, Clus::Facade::Grouping& grouping) const +{ + using namespace WireCell::Clus; + + auto tf = grouping.get_track_fitting(); + if (!tf) { + log->warn("UbooneMagnifyTrackingVisitor: no TrackFitting in grouping"); + return; + } + + auto graph = tf->get_graph(); + if (!graph) { + log->warn("UbooneMagnifyTrackingVisitor: no Graph in TrackFitting"); + return; + } + + // Get nticks_per_slice for time scaling + auto nticks_map = grouping.get_nticks_per_slice(); + int nticks_per_slice = 1; // default value + if (!nticks_map.empty()) { + // Use the first available nticks_per_slice value (for uBooNE single APA) + nticks_per_slice = nticks_map.begin()->second.begin()->second; + } + + // Create the point tree structure + WCPointTree point_tree; + + // Create TTree with branches + TTree* t_rec_charge = new TTree("T_rec_charge", "T_rec_charge"); + t_rec_charge->SetDirectory(output_tf); + t_rec_charge->Branch("x", &point_tree.reco_x, "x/D"); + t_rec_charge->Branch("y", &point_tree.reco_y, "y/D"); + t_rec_charge->Branch("z", &point_tree.reco_z, "z/D"); + t_rec_charge->Branch("q", &point_tree.reco_dQ, "q/D"); + t_rec_charge->Branch("nq", &point_tree.reco_dx, "nq/D"); + t_rec_charge->Branch("chi2", &point_tree.reco_chi2, "chi2/D"); + t_rec_charge->Branch("ndf", &point_tree.reco_ndf, "ndf/D"); + t_rec_charge->Branch("pu", &point_tree.reco_pu, "pu/D"); + t_rec_charge->Branch("pv", &point_tree.reco_pv, "pv/D"); + t_rec_charge->Branch("pw", &point_tree.reco_pw, "pw/D"); + t_rec_charge->Branch("pt", &point_tree.reco_pt, "pt/D"); + t_rec_charge->Branch("reduced_chi2", &point_tree.reco_reduced_chi2, "reduced_chi2/D"); + t_rec_charge->Branch("flag_vertex", &point_tree.reco_flag_vertex, "flag_vertex/I"); + t_rec_charge->Branch("flag_shower", &point_tree.reco_flag_track_shower, "flag_shower/I"); + t_rec_charge->Branch("rr", &point_tree.reco_rr, "rr/D"); + t_rec_charge->Branch("cluster_id", &point_tree.reco_mother_cluster_id, "cluster_id/I"); + t_rec_charge->Branch("real_cluster_id", &point_tree.reco_cluster_id, "real_cluster_id/I"); + t_rec_charge->Branch("sub_cluster_id", &point_tree.reco_proto_cluster_id, "sub_cluster_id/I"); + t_rec_charge->Branch("particle_id", &point_tree.reco_particle_id, "particle_id/I"); + + // Use calibration parameters from configuration + const double dQdx_scale = m_dQdx_scale; + const double dQdx_offset = m_dQdx_offset; + const bool flag_skip_vertex = m_flag_skip_vertex; + + // Set default values + point_tree.reco_chi2 = 1; + + // Collect all clusters from the graph + std::set all_clusters; + + // Iterate through all edges (segments) in the graph + auto edge_range = boost::edges(*graph); + for (auto eit = edge_range.first; eit != edge_range.second; ++eit) { + auto seg = (*graph)[*eit].segment; + if (seg && seg->cluster()) { + all_clusters.insert(seg->cluster()); + } + } + + // Iterate through all vertices in the graph + auto vertex_range = boost::vertices(*graph); + for (auto vit = vertex_range.first; vit != vertex_range.second; ++vit) { + auto vtx = (*graph)[*vit].vertex; + if (vtx && vtx->cluster()) { + all_clusters.insert(vtx->cluster()); + } + } + + // Find the main cluster ID + int mother_cluster_id = -1; + for (auto* cluster : all_clusters) { + if (cluster && cluster->get_flag(Facade::Flags::main_cluster)) { + mother_cluster_id = cluster->get_cluster_id(); + break; + } + } + + // Process each cluster + for (auto* cluster : all_clusters) { + if (!cluster) continue; + + point_tree.reco_mother_cluster_id = mother_cluster_id; + + // Process vertices in this cluster + if (!flag_skip_vertex) { + for (auto vit = vertex_range.first; vit != vertex_range.second; ++vit) { + auto vtx = (*graph)[*vit].vertex; + if (!vtx || vtx->cluster() != cluster) continue; + + // Fill vertex information + point_tree.reco_cluster_id = cluster->get_cluster_id(); + point_tree.reco_proto_cluster_id = -1; + point_tree.reco_particle_id = -1; + point_tree.reco_ndf = cluster->get_cluster_id(); + point_tree.reco_flag_vertex = 1; + point_tree.reco_flag_track_shower = 0; + + // Position from fit + const auto& fit_pt = vtx->fit().point; + point_tree.reco_x = fit_pt.x() / units::cm; + point_tree.reco_y = fit_pt.y() / units::cm; + point_tree.reco_z = fit_pt.z() / units::cm; + + // Charge and step size from fit + point_tree.reco_dQ = vtx->fit().dQ * dQdx_scale + dQdx_offset; + point_tree.reco_dx = vtx->fit().dx / units::cm; + + // Projection coordinates + point_tree.reco_pu = vtx->fit().pu; + point_tree.reco_pv = vtx->fit().pv; + point_tree.reco_pw = vtx->fit().pw; + point_tree.reco_pt = vtx->fit().pt / nticks_per_slice; + + point_tree.reco_reduced_chi2 = vtx->fit().reduced_chi2; + point_tree.reco_rr = -1; // no residual range for vertices + + t_rec_charge->Fill(); + } + } + + // Process segments in this cluster + for (auto eit = edge_range.first; eit != edge_range.second; ++eit) { + auto seg = (*graph)[*eit].segment; + if (!seg || seg->cluster() != cluster) continue; + + const auto& fits = seg->fits(); + const auto& wcpts = seg->wcpts(); + + if (fits.empty() || wcpts.empty()) continue; + + point_tree.reco_cluster_id = cluster->get_cluster_id(); // cluster id ... + point_tree.reco_ndf = cluster->get_cluster_id(); + point_tree.reco_proto_cluster_id = cluster->get_cluster_id() * 1000 + seg->id(); + point_tree.reco_flag_vertex = 0; + + // Determine track/shower flag + bool is_shower = seg->flags_any(PR::SegmentFlags::kShowerTrajectory) || + seg->flags_any(PR::SegmentFlags::kShowerTopology); + point_tree.reco_flag_track_shower = is_shower ? 1 : 0; + + // Set particle ID (simple classification: shower=1, track=4) + point_tree.reco_particle_id = is_shower ? 1 : 4; + + // Calculate residual range vector + std::vector rr_vec(fits.size(), 0); + { + std::vector L(fits.size(), 0); + double acc_length = 0; + + for (size_t i = 0; i + 1 < fits.size(); i++) { + const auto& p1 = fits[i].point; + const auto& p2 = fits[i+1].point; + double step = std::sqrt(std::pow(p2.x() - p1.x(), 2) + + std::pow(p2.y() - p1.y(), 2) + + std::pow(p2.z() - p1.z(), 2)); + acc_length += step; + L[i+1] = acc_length; + } + + // Direction sign determines order + int dirsign = seg->dirsign(); + if (dirsign == 1) { // forward direction + for (size_t i = 0; i < fits.size(); i++) { + rr_vec[fits.size() - 1 - i] = L.back() - L[fits.size() - 1 - i]; + } + } else if (dirsign == -1) { // reverse direction + rr_vec = L; + } else { // unknown direction + rr_vec = L; + } + + // Find vertices connected to this segment + auto [start_vtx, end_vtx] = PR::find_vertices(*graph, seg); + + // Check if vertices have multiple connections + if (start_vtx) { + auto start_degree = boost::out_degree(start_vtx->get_descriptor(), *graph); + if (start_degree > 1) rr_vec.front() = -1; + } + if (end_vtx) { + auto end_degree = boost::out_degree(end_vtx->get_descriptor(), *graph); + if (end_degree > 1) rr_vec.back() = -1; + } + } + + // Fill tree for each point in the segment + for (size_t i = 0; i < fits.size(); i++) { + const auto& fit = fits[i]; + + point_tree.reco_x = fit.point.x() / units::cm; + point_tree.reco_y = fit.point.y() / units::cm; + point_tree.reco_z = fit.point.z() / units::cm; + point_tree.reco_dQ = fit.dQ * dQdx_scale + dQdx_offset; + point_tree.reco_dx = fit.dx / units::cm; + point_tree.reco_pu = fit.pu; + point_tree.reco_pv = fit.pv; + point_tree.reco_pw = fit.pw; + point_tree.reco_pt = fit.pt / nticks_per_slice; + point_tree.reco_reduced_chi2 = fit.reduced_chi2; + point_tree.reco_rr = rr_vec[i] / units::cm; + + t_rec_charge->Fill(); + } + } + } + + log->debug("UbooneMagnifyTrackingVisitor: wrote {} entries to T_rec_charge", t_rec_charge->GetEntries()); +} + +// Local Variables: +// mode: c++ +// c-basic-offset: 4 +// End: diff --git a/root/wscript_build b/root/wscript_build index e3145e0e8..b17512687 100644 --- a/root/wscript_build +++ b/root/wscript_build @@ -1,4 +1,5 @@ bld.smplpkg('WireCellRoot', - use='WireCellAux ROOTSYS', + use='WireCellAux WireCellClus ROOTSYS', + app_use='WireCellAux ROOTSYS', test_use='WireCellGen WireCellSigProc') diff --git a/util/inc/WireCellUtil/Flagged.h b/util/inc/WireCellUtil/Flagged.h index 91dcf9f93..c7d78aa23 100644 --- a/util/inc/WireCellUtil/Flagged.h +++ b/util/inc/WireCellUtil/Flagged.h @@ -56,10 +56,19 @@ namespace WireCell { if (index < max_flags) { m_flags |= ( 1ULL << index); }; } + /// Clear a specific bit to false. If index is out of bounds, this is a quiet no-op. + void unset_flag(index_t index) { + if (index < max_flags) { m_flags &= ~( 1ULL << index); }; + } + /// Set any number of flags. The bits set in "flags" are set in our /// internal flags. Any existing set flags are kept. void set_flags(FlagsType flags) { m_flags |= static_cast(flags); } + /// Clear any number of flags. The bits set in "flags" are cleared in our + /// internal flags. Other existing flags are kept. + void unset_flags(FlagsType flags) { m_flags &= ~static_cast(flags); } + /// Keep only the set flags in "flags" that are also set in our flags. /// New flags are not set, this disable existing flags not in set in the /// input. diff --git a/util/inc/WireCellUtil/Point.h b/util/inc/WireCellUtil/Point.h index d23340db1..5fd472909 100644 --- a/util/inc/WireCellUtil/Point.h +++ b/util/inc/WireCellUtil/Point.h @@ -84,6 +84,18 @@ namespace WireCell { * projected onto the ray's direction. */ double ray_dist(const Ray& ray, const Point& point); + /** Return the perpendicular distance from a point to the infinite line + * defined by the ray. This is the shortest distance from the point to the line. */ + double ray_closest_dis(const Ray& ray, const Point& point); + + /** Return the shortest distance between two infinite lines defined by the rays. + * For skew lines, this is the distance between the two closest approach points. */ + double ray_closest_dis(const Ray& ray1, const Ray& ray2); + + /** Return a pair of points representing the closest points on two infinite lines + * defined by the rays. The first point is on ray1, the second on ray2. */ + std::pair ray_closest_points(const Ray& ray1, const Ray& ray2); + /** Return the volume of a box aligned with axes and with the ray * at opposite corners. */ double ray_volume(const Ray& ray); diff --git a/util/src/Point.cxx b/util/src/Point.cxx index 20af585a4..0a81bf665 100644 --- a/util/src/Point.cxx +++ b/util/src/Point.cxx @@ -84,6 +84,92 @@ double WireCell::ray_dist(const WireCell::Ray& ray, const WireCell::Point& point return ray_unit(ray).dot(point - ray.first); } +double WireCell::ray_closest_dis(const WireCell::Ray& ray, const WireCell::Point& point) +{ + // Perpendicular distance from point to line + // Calculate vectors from point to both ray endpoints + WireCell::Vector d1 = point - ray.first; // point to tail + WireCell::Vector d2 = point - ray.second; // point to head + + // Cross product gives a vector perpendicular to the plane containing the point and line + WireCell::Vector cross = d1.cross(d2); + + // Distance = |cross product| / |direction vector| + WireCell::Vector dir = ray_vector(ray); + return cross.magnitude() / dir.magnitude(); +} + +double WireCell::ray_closest_dis(const WireCell::Ray& ray1, const WireCell::Ray& ray2) +{ + // Distance between two skew lines + // ca = vector from ray2.first to ray1.first + WireCell::Vector ca = ray1.first - ray2.first; + + // Get direction vectors + WireCell::Vector dir1 = ray_vector(ray1); + WireCell::Vector dir2 = ray_vector(ray2); + + // Cross product of direction vectors + WireCell::Vector bd = dir1.cross(dir2); + + // Distance = |ca · (dir1 × dir2)| / |dir1 × dir2| + double bd_mag = bd.magnitude(); + if (bd_mag < 1e-6) { + // Lines are parallel, use point-to-line distance + return ray_closest_dis(ray1, ray2.first); + } + + return std::abs(ca.dot(bd) / bd_mag); +} + +std::pair WireCell::ray_closest_points(const WireCell::Ray& ray1, const WireCell::Ray& ray2) +{ + // Find the closest points on two infinite lines + // Using the algorithm similar to WCP's closest_dis_points + + WireCell::Vector d = ray1.first - ray2.first; + WireCell::Vector dir1 = ray_vector(ray1); + WireCell::Vector dir2 = ray_vector(ray2); + + // Normal vector to both lines + WireCell::Vector c = dir1.cross(dir2); + double c_mag = c.magnitude(); + + if (c_mag < 1e-6) { + // Lines are parallel, return arbitrary closest points + return std::make_pair(ray1.first, ray2.first); + } + + WireCell::Vector c_unit = c.norm(); + + // Project d onto dir2 and calculate rejection + double proj_mag = d.dot(dir2) / dir2.magnitude(); + WireCell::Vector proj = proj_mag * dir2.norm(); + WireCell::Vector rej = d - proj - (d.dot(c_unit) / c_mag) * c_unit; + + // Find point on ray1 + WireCell::Vector dir1_unit = dir1.norm(); + double rej_mag = rej.magnitude(); + double scale1 = rej_mag / dir1_unit.dot(rej.norm()); + WireCell::Point tp1 = ray1.first - scale1 * dir1_unit; + + // Now find point on ray2 + WireCell::Vector d_p = d * (-1.0); + WireCell::Vector c_p = c * (-1.0); + WireCell::Vector c_p_unit = c_p.norm(); + + double proj_p_mag = d_p.dot(dir1) / dir1.magnitude(); + WireCell::Vector proj_p = proj_p_mag * dir1.norm(); + WireCell::Vector rej_p = d_p - proj_p - (d_p.dot(c_p_unit) / c_mag) * c_p_unit; + + WireCell::Vector dir2_unit = dir2.norm(); + double rej_p_mag = rej_p.magnitude(); + double scale2 = rej_p_mag / dir2_unit.dot(rej_p.norm()); + WireCell::Point tp2 = ray2.first - scale2 * dir2_unit; + + return std::make_pair(tp1, tp2); +} + double WireCell::ray_volume(const WireCell::Ray& ray) { auto diff = ray_vector(ray);