Zigzag engine (6802 lines, 184 tests): - Construction 17 normalisation: working through dimension 3+ - Import from homotopy-rs JSON: working (scalar, two_scalars, half_braid) - Piece extraction via Embedding/restrict_diagram: working - Type checking pipeline: working (Eckmann-Hilton half_braid passes) - Essential identity detection: validated with full 2-diagram test Bugs found and fixed: - assemble_factorisations losing cospan legs during reassembly - RewriteN::slice() using source offsets instead of target indices - singular_preimage() not handling passthrough heights - restrict_rewrite() not accounting for accumulated cone offsets - Embedding::preimage() using regular_preimage for Singular case Added vis-engine-spec.md: visualization engine specification - 6-layer architecture from math primitives to scene graph - SVG renderer for 2D, WebGL2 for 3D, custom hit testing - Spring constraint integration point for semiotic rendering - No external dependencies - game engine approach Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
330 lines
15 KiB
Rust
330 lines
15 KiB
Rust
//! 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::<Vec<_>>()
|
|
.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<f64> {
|
|
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<usize> = 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<usize> = 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<String, Vec<usize>> = 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<usize> = 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<usize>> = vec![vec![]; pts.len()];
|
|
let mut predecessors: Vec<Vec<usize>> = 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<String> = 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!("════════════════════════════════════════════════════════════════════════════════");
|
|
}
|