//! Complete scaffold analysis for half_braid //! //! Run with: cargo run --example scaffold_analysis use std::fs; use zigzag_engine::diagram::Diagram; use zigzag_engine::explosion::{HeightLabel, Point}; use zigzag_engine::import::load_homotopy_diagram_n; /// Format a point as a string like "r0,s0,s1" fn format_point(p: &Point) -> String { p.0.iter() .map(|h| match h { HeightLabel::Regular(j) => format!("r{}", j), HeightLabel::Singular(j) => format!("s{}", j), }) .collect::>() .join(",") } /// Count singular labels in a point fn singular_count(p: &Point) -> usize { p.0.iter().filter(|h| h.is_singular()).count() } /// Compute geometric dimension: n - singular_count fn geom_dim(p: &Point, n: usize) -> usize { n - singular_count(p) } /// Naive layout position: regular -> integer, singular -> half-integer fn naive_layout(p: &Point) -> Vec { p.0.iter() .map(|h| match h { HeightLabel::Regular(j) => *j as f64, HeightLabel::Singular(j) => *j as f64 + 0.5, }) .collect() } /// Describe which coordinate changed between two points fn describe_change(lower: &Point, upper: &Point) -> String { for (i, (l, u)) in lower.0.iter().zip(upper.0.iter()).enumerate() { if l != u { let l_str = match l { HeightLabel::Regular(j) => format!("r{}", j), HeightLabel::Singular(j) => format!("s{}", j), }; let u_str = match u { HeightLabel::Regular(j) => format!("r{}", j), HeightLabel::Singular(j) => format!("s{}", j), }; return format!("coord[{}]: {} → {}", i, l_str, u_str); } } "no change".to_string() } /// Get time slice label from coord[2] fn time_slice(p: &Point) -> String { match p.0.get(2) { Some(HeightLabel::Regular(0)) => "r0 (source)".to_string(), Some(HeightLabel::Singular(0)) => "s0 (merge)".to_string(), Some(HeightLabel::Regular(1)) => "r1 (target)".to_string(), Some(h) => format!("{:?}", h), None => "N/A".to_string(), } } fn main() { // Load diagram let json = fs::read_to_string("fixtures/half_braid.json") .expect("Failed to read fixtures/half_braid.json"); let diagram_n = load_homotopy_diagram_n(&json) .expect("Failed to parse half_braid.json"); let diagram = Diagram::DiagramN(diagram_n); let n = diagram.dimension(); let pts = diagram.full_points(); println!("════════════════════════════════════════════════════════════════════════════════"); println!("COMPLETE SCAFFOLD ANALYSIS FOR half_braid.json"); println!("════════════════════════════════════════════════════════════════════════════════"); println!(); println!("Diagram dimension: {}", n); println!("Total points: {}", pts.len()); println!("Total covering relations: {}", pts.covers().len()); println!(); // ========================================================================= // SECTION 1: All 23 Points // ========================================================================= println!("════════════════════════════════════════════════════════════════════════════════"); println!("SECTION 1: ALL {} POINTS", pts.len()); println!("════════════════════════════════════════════════════════════════════════════════"); println!(); println!("{:>3} {:>12} {:>4} {:>8} {:>8} {:>20}", "idx", "coords", "sing", "geom_dim", "visible", "naive_layout"); println!("{}", "-".repeat(80)); for (idx, point) in pts.elements().iter().enumerate() { let sc = singular_count(point); let gd = geom_dim(point, n); let vis = point.is_visible(n); let layout = naive_layout(point); let layout_str = format!("({:.1}, {:.1}, {:.1})", layout[0], layout[1], layout[2]); println!("{:>3} {:>12} {:>4} {:>8} {:>8} {:>20}", idx, format_point(point), sc, gd, if vis { "YES" } else { "no" }, layout_str); } println!(); // ========================================================================= // SECTION 2: All 35 Covering Relations // ========================================================================= println!("════════════════════════════════════════════════════════════════════════════════"); println!("SECTION 2: ALL {} COVERING RELATIONS", pts.covers().len()); println!("════════════════════════════════════════════════════════════════════════════════"); println!(); println!("{:>3} {:>12} → {:>12} {:>20}", "#", "lower", "upper", "change"); println!("{}", "-".repeat(60)); for (i, &(lower_idx, upper_idx)) in pts.covers().iter().enumerate() { let lower = &pts.elements()[lower_idx]; let upper = &pts.elements()[upper_idx]; let change = describe_change(lower, upper); println!("{:>3} {:>12} → {:>12} {:>20}", i + 1, format!("{}:{}", lower_idx, format_point(lower)), format!("{}:{}", upper_idx, format_point(upper)), change); } println!(); // ========================================================================= // SECTION 3: Visible Elements and Their Connections // ========================================================================= println!("════════════════════════════════════════════════════════════════════════════════"); println!("SECTION 3: VISIBLE ELEMENTS AND THEIR CONNECTIONS"); println!("════════════════════════════════════════════════════════════════════════════════"); println!(); let visible_indices: Vec = pts.elements() .iter() .enumerate() .filter(|(_, point)| point.is_visible(n)) .map(|(idx, _)| idx) .collect(); println!("Visible elements: {} total", visible_indices.len()); println!(); // Group by geometric dimension for gd in 0..=n { let elements: Vec = visible_indices.iter() .filter(|&&idx| geom_dim(&pts.elements()[idx], n) == gd) .copied() .collect(); if elements.is_empty() { continue; } let gd_name = match gd { 0 => "VERTICES (0-dim)", 1 => "WIRES (1-dim)", 2 => "SURFACES (2-dim)", 3 => "VOLUMES (3-dim)", _ => "HIGHER", }; println!("--- {} ---", gd_name); println!(); for idx in elements { let point = &pts.elements()[idx]; let preds = pts.immediate_predecessors(idx); let succs = pts.immediate_successors(idx); println!(" [{:>2}] {} = ({:.1}, {:.1}, {:.1})", idx, format_point(point), naive_layout(point)[0], naive_layout(point)[1], naive_layout(point)[2]); if !preds.is_empty() { println!(" predecessors (covered by this):"); for p_idx in &preds { let p = &pts.elements()[*p_idx]; let vis = if p.is_visible(n) { " [VIS]" } else { "" }; println!(" [{:>2}] {}{}", p_idx, format_point(p), vis); } } if !succs.is_empty() { println!(" successors (covers this):"); for s_idx in &succs { let s = &pts.elements()[*s_idx]; let vis = if s.is_visible(n) { " [VIS]" } else { "" }; println!(" [{:>2}] {}{}", s_idx, format_point(s), vis); } } println!(); } } // ========================================================================= // SECTION 4: Points Grouped by Time Slice // ========================================================================= println!("════════════════════════════════════════════════════════════════════════════════"); println!("SECTION 4: POINTS GROUPED BY TIME SLICE (coord[2])"); println!("════════════════════════════════════════════════════════════════════════════════"); println!(); // Group points by time slice let mut by_time: std::collections::HashMap> = std::collections::HashMap::new(); for (idx, point) in pts.elements().iter().enumerate() { let ts = time_slice(point); by_time.entry(ts).or_default().push(idx); } for time_label in &["r0 (source)", "s0 (merge)", "r1 (target)"] { if let Some(indices) = by_time.get(*time_label) { println!("--- TIME {} ---", time_label); println!(" {} points at this time slice:", indices.len()); for &idx in indices { let point = &pts.elements()[idx]; let vis = if point.is_visible(n) { " [VISIBLE]" } else { "" }; let gd = geom_dim(point, n); let gd_str = match gd { 0 => "vertex", 1 => "wire", 2 => "surface", 3 => "volume", _ => "?", }; println!(" [{:>2}] {:>12} (geom_dim={}, {}){}", idx, format_point(point), gd, gd_str, vis); } println!(); } } // ========================================================================= // SECTION 5: Scaffold Node Paths for Visible Wires // ========================================================================= println!("════════════════════════════════════════════════════════════════════════════════"); println!("SECTION 5: SCAFFOLD NODE PATHS FOR VISIBLE WIRES"); println!("════════════════════════════════════════════════════════════════════════════════"); println!(); let wire_indices: Vec = visible_indices.iter() .filter(|&&idx| geom_dim(&pts.elements()[idx], n) == 1) .copied() .collect(); println!("Visible wires trace through the scaffold via covering relations."); println!("Each wire has geom_dim=1 (one regular coordinate)."); println!(); for idx in wire_indices { let point = &pts.elements()[idx]; let layout = naive_layout(point); println!("WIRE [{:>2}] {} at ({:.1}, {:.1}, {:.1})", idx, format_point(point), layout[0], layout[1], layout[2]); // Find all reachable points in both directions (full path through scaffold) let preds = pts.immediate_predecessors(idx); let succs = pts.immediate_successors(idx); println!(" Direct connections:"); for p_idx in &preds { let p = &pts.elements()[*p_idx]; let vis = if p.is_visible(n) { " [VIS]" } else { "" }; let p_layout = naive_layout(p); println!(" ↓ [{:>2}] {} at ({:.1},{:.1},{:.1}){}", p_idx, format_point(p), p_layout[0], p_layout[1], p_layout[2], vis); } println!(" ● [{:>2}] {} (this wire)", idx, format_point(point)); for s_idx in &succs { let s = &pts.elements()[*s_idx]; let vis = if s.is_visible(n) { " [VIS]" } else { "" }; let s_layout = naive_layout(s); println!(" ↑ [{:>2}] {} at ({:.1},{:.1},{:.1}){}", s_idx, format_point(s), s_layout[0], s_layout[1], s_layout[2], vis); } println!(); } // ========================================================================= // SECTION 6: Adjacency Matrix (abbreviated) // ========================================================================= println!("════════════════════════════════════════════════════════════════════════════════"); println!("SECTION 6: COVER ADJACENCY (which points cover which)"); println!("════════════════════════════════════════════════════════════════════════════════"); println!(); // Build adjacency let mut successors: Vec> = vec![vec![]; pts.len()]; let mut predecessors: Vec> = vec![vec![]; pts.len()]; for &(lower, upper) in pts.covers() { successors[lower].push(upper); predecessors[upper].push(lower); } println!("Point → Immediate Successors (covered by)"); println!("{}", "-".repeat(50)); for (idx, succs) in successors.iter().enumerate() { if !succs.is_empty() { let point = &pts.elements()[idx]; let succs_str: Vec = succs.iter() .map(|&s| format!("{}:{}", s, format_point(&pts.elements()[s]))) .collect(); println!("[{:>2}] {:>12} → [{}]", idx, format_point(point), succs_str.join(", ")); } } println!(); println!("════════════════════════════════════════════════════════════════════════════════"); println!("END OF SCAFFOLD ANALYSIS"); println!("════════════════════════════════════════════════════════════════════════════════"); }