Compare commits
No commits in common. "main" and "feat/slice-computation" have entirely different histories.
main
...
feat/slice
36 changed files with 166 additions and 144037 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -1,3 +1 @@
|
|||
target/
|
||||
*.swp
|
||||
*.swo
|
||||
/target
|
||||
|
|
|
|||
3
Cargo.lock
generated
3
Cargo.lock
generated
|
|
@ -346,7 +346,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -633,8 +632,6 @@ name = "zigzag-engine"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"proptest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ license = "BSD-3-Clause"
|
|||
|
||||
[dependencies]
|
||||
thiserror = "1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
proptest = "1"
|
||||
|
|
|
|||
|
|
@ -1,94 +0,0 @@
|
|||
//! Inspect the categorical structure of half_braid.json
|
||||
//!
|
||||
//! Run with: cargo run --example inspect_half_braid
|
||||
|
||||
use std::fs;
|
||||
use zigzag_engine::diagram::Diagram;
|
||||
use zigzag_engine::import::load_homotopy_diagram_n;
|
||||
|
||||
fn describe_diagram(d: &Diagram, indent: usize) -> String {
|
||||
let prefix = " ".repeat(indent);
|
||||
match d {
|
||||
Diagram::Diagram0(g) => {
|
||||
format!("{}0-diagram: generator id={}, dim={}", prefix, g.id, g.dimension)
|
||||
}
|
||||
Diagram::DiagramN(dn) => {
|
||||
let dim = d.dimension();
|
||||
let mut lines = vec![format!(
|
||||
"{}{}-diagram with {} cospans:",
|
||||
prefix,
|
||||
dim,
|
||||
dn.cospans.len()
|
||||
)];
|
||||
lines.push(format!("{} source:", prefix));
|
||||
lines.push(describe_diagram(dn.source(), indent + 2));
|
||||
if !dn.cospans.is_empty() {
|
||||
lines.push(format!("{} target:", prefix));
|
||||
lines.push(describe_diagram(&dn.target(), indent + 2));
|
||||
}
|
||||
lines.join("\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let json = fs::read_to_string("fixtures/half_braid.json")
|
||||
.expect("Failed to read half_braid.json");
|
||||
let half_braid = load_homotopy_diagram_n(&json)
|
||||
.expect("Failed to parse");
|
||||
|
||||
let half_braid_d = Diagram::DiagramN(half_braid.clone());
|
||||
|
||||
println!("=== HALF_BRAID CATEGORICAL STRUCTURE ===\n");
|
||||
|
||||
// The half_braid itself is a 3-diagram
|
||||
println!("half_braid is a {}-diagram with {} cospans\n",
|
||||
half_braid_d.dimension(), half_braid.cospans.len());
|
||||
|
||||
// Its source (a 2-diagram)
|
||||
let source_2d = half_braid.source();
|
||||
println!("SOURCE of half_braid (the 2-diagram it transforms FROM):");
|
||||
println!("{}\n", describe_diagram(source_2d, 1));
|
||||
|
||||
// Its target (a 2-diagram)
|
||||
let target_2d = half_braid.target();
|
||||
println!("TARGET of half_braid (the 2-diagram it transforms TO):");
|
||||
println!("{}\n", describe_diagram(&target_2d, 1));
|
||||
|
||||
// Are source and target the same?
|
||||
println!("Are source and target equal? {}\n", source_2d == &target_2d);
|
||||
|
||||
// Look at the source 2-diagram structure
|
||||
if let Diagram::DiagramN(src) = source_2d {
|
||||
println!("=== SOURCE 2-DIAGRAM SLICES ===");
|
||||
println!("This 2-diagram has {} cospans (singular heights)\n", src.cospans.len());
|
||||
|
||||
// Regular slices
|
||||
for i in 0..=src.cospans.len() {
|
||||
if let Some(slice) = src.regular_slice(i) {
|
||||
println!("Regular slice r{}: {}", i, describe_diagram(&slice, 0));
|
||||
}
|
||||
}
|
||||
println!();
|
||||
|
||||
// Singular slices
|
||||
for i in 0..src.cospans.len() {
|
||||
if let Some(slice) = src.singular_slice(i) {
|
||||
println!("Singular slice s{}: {}", i, describe_diagram(&slice, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("\n=== INTERPRETATION ===");
|
||||
println!("Generator 0 (dim=0): The base object x");
|
||||
println!("Generator 1 (dim=2): The scalar s (a 2-cell: id_x → id_x)");
|
||||
println!();
|
||||
println!("The SOURCE 2-diagram is 'two scalars stacked':");
|
||||
println!(" - 2 cospans means 2 singular heights (s0, s1)");
|
||||
println!(" - Each singular height is where a scalar (2-cell) lives");
|
||||
println!();
|
||||
println!("The half_braid 3-diagram is the Eckmann-Hilton homotopy:");
|
||||
println!(" - It shows the two scalars 'sliding past' each other");
|
||||
println!(" - Source = target (as 2-diagrams, they're the same configuration)");
|
||||
println!(" - But the 3-diagram is NON-trivial: it's the braiding coherence");
|
||||
}
|
||||
|
|
@ -1,287 +0,0 @@
|
|||
//! Generate geometry JSON from explosion for Three.js rendering.
|
||||
//!
|
||||
//! Run with: cargo run --example render_braiding
|
||||
//!
|
||||
//! Outputs fixtures/half_braid_geometry.json with VISIBLE elements only.
|
||||
//! Visibility follows homotopy.io's rule: a point at geom_dim d is visible
|
||||
//! iff coords[d..] are all singular.
|
||||
|
||||
use std::collections::{HashMap, HashSet, VecDeque};
|
||||
use std::fs;
|
||||
|
||||
use zigzag_engine::diagram::Diagram;
|
||||
use zigzag_engine::explosion::{HeightLabel, Point};
|
||||
use zigzag_engine::import::load_homotopy_diagram_n;
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Geometry {
|
||||
metadata: Metadata,
|
||||
vertices: Vec<Vertex>,
|
||||
wires: Vec<Wire>,
|
||||
surfaces: Vec<Surface>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Metadata {
|
||||
source: String,
|
||||
dimension: usize,
|
||||
total_points: usize,
|
||||
visible_points: usize,
|
||||
total_covers: usize,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Vertex {
|
||||
id: usize,
|
||||
label: String,
|
||||
point: String,
|
||||
coords: [f64; 3],
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Wire {
|
||||
id: usize,
|
||||
label: String,
|
||||
point: String,
|
||||
coords: [f64; 3],
|
||||
endpoints: [usize; 2],
|
||||
endpoint_coords: [[f64; 3]; 2],
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Surface {
|
||||
id: usize,
|
||||
label: String,
|
||||
point: String,
|
||||
coords: [f64; 3],
|
||||
boundary_wires: Vec<usize>,
|
||||
}
|
||||
|
||||
/// Format a point as a string like "s0,s1,r0"
|
||||
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(",")
|
||||
}
|
||||
|
||||
/// Compute layout coordinates for rendering.
|
||||
///
|
||||
/// For half_braid visible elements:
|
||||
/// - coord[0] (depth/strand): r0→-1, r1→0, r2→1 for wires; s0→-0.5, s1→0.5 for vertices
|
||||
/// - coord[1] (height): r0→-1, s0→0, r1→1 for layout y
|
||||
/// - coord[2] (time): s0→0 for visible elements (all at crossing time)
|
||||
///
|
||||
/// Output: [x, y, z] where:
|
||||
/// - x = time (all 0 for visible crossing slice)
|
||||
/// - y = height
|
||||
/// - z = depth (spread wires/vertices along this axis)
|
||||
fn layout_coords(p: &Point) -> [f64; 3] {
|
||||
// For the visible crossing slice, all elements have coord[2] = s0 (time = singular)
|
||||
// So x (time) = 0 for all visible elements
|
||||
|
||||
// z = depth axis (coord[0])
|
||||
let z = match &p.0[0] {
|
||||
HeightLabel::Regular(j) => (*j as f64) - 1.0, // r0→-1, r1→0, r2→1
|
||||
HeightLabel::Singular(j) => (*j as f64) - 0.5, // s0→-0.5, s1→0.5
|
||||
};
|
||||
|
||||
// y = height axis (coord[1])
|
||||
let y = match &p.0[1] {
|
||||
HeightLabel::Regular(j) => (*j as f64) - 1.0, // r0→-1, r1→1
|
||||
HeightLabel::Singular(j) => *j as f64, // s0→0
|
||||
};
|
||||
|
||||
// x = time axis (coord[2]) - all visible elements are at s0
|
||||
let x = 0.0;
|
||||
|
||||
[x, y, z]
|
||||
}
|
||||
|
||||
/// Count singular labels in a point
|
||||
fn singular_count(p: &Point) -> usize {
|
||||
p.0.iter().filter(|h| h.is_singular()).count()
|
||||
}
|
||||
|
||||
/// Compute geometric dimension from singular count
|
||||
fn geom_dim(p: &Point, n: usize) -> usize {
|
||||
n - singular_count(p)
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
eprintln!("Loaded half_braid.json: dim={}, {} points, {} covers",
|
||||
n, pts.len(), pts.covers().len());
|
||||
|
||||
// Filter to VISIBLE points only
|
||||
let visible_indices: Vec<usize> = pts.elements()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, point)| point.is_visible(n))
|
||||
.map(|(idx, _)| idx)
|
||||
.collect();
|
||||
|
||||
eprintln!("Visible points: {}", visible_indices.len());
|
||||
|
||||
// Group visible points by geometric dimension
|
||||
let mut by_geom_dim: HashMap<usize, Vec<usize>> = HashMap::new();
|
||||
for &idx in &visible_indices {
|
||||
let point = &pts.elements()[idx];
|
||||
let gd = geom_dim(point, n);
|
||||
by_geom_dim.entry(gd).or_default().push(idx);
|
||||
}
|
||||
|
||||
// Build adjacency for reachability (on full poset)
|
||||
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);
|
||||
}
|
||||
|
||||
// Helper: find all transitively reachable points
|
||||
let reachable_from = |start: usize, adj: &[Vec<usize>]| -> HashSet<usize> {
|
||||
let mut visited = HashSet::new();
|
||||
let mut queue = VecDeque::new();
|
||||
queue.push_back(start);
|
||||
visited.insert(start);
|
||||
while let Some(curr) = queue.pop_front() {
|
||||
for &next in &adj[curr] {
|
||||
if visited.insert(next) {
|
||||
queue.push_back(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
visited
|
||||
};
|
||||
|
||||
// Get visible vertex indices
|
||||
let vertex_indices = by_geom_dim.get(&0).map(|v| v.as_slice()).unwrap_or(&[]);
|
||||
let vertex_set: HashSet<usize> = vertex_indices.iter().copied().collect();
|
||||
|
||||
// Get visible wire indices
|
||||
let wire_indices = by_geom_dim.get(&1).map(|v| v.as_slice()).unwrap_or(&[]);
|
||||
let wire_set: HashSet<usize> = wire_indices.iter().copied().collect();
|
||||
|
||||
// Build vertices
|
||||
let mut vertices: Vec<Vertex> = Vec::new();
|
||||
for (i, &idx) in vertex_indices.iter().enumerate() {
|
||||
let point = &pts.elements()[idx];
|
||||
vertices.push(Vertex {
|
||||
id: idx,
|
||||
label: format!("vertex_{}", i),
|
||||
point: format_point(point),
|
||||
coords: layout_coords(point),
|
||||
});
|
||||
}
|
||||
|
||||
// Build wires with endpoint connections
|
||||
let mut wires: Vec<Wire> = Vec::new();
|
||||
for (i, &idx) in wire_indices.iter().enumerate() {
|
||||
let point = &pts.elements()[idx];
|
||||
|
||||
// Find connected VISIBLE vertices via transitive reachability
|
||||
let reachable_up = reachable_from(idx, &successors);
|
||||
let reachable_down = reachable_from(idx, &predecessors);
|
||||
|
||||
let mut connected: Vec<usize> = reachable_up
|
||||
.union(&reachable_down)
|
||||
.filter(|v| vertex_set.contains(v))
|
||||
.copied()
|
||||
.collect();
|
||||
connected.sort();
|
||||
connected.dedup();
|
||||
|
||||
let endpoints = if connected.len() >= 2 {
|
||||
[connected[0], connected[1]]
|
||||
} else if connected.len() == 1 {
|
||||
[connected[0], connected[0]]
|
||||
} else {
|
||||
[vertex_indices[0], vertex_indices.get(1).copied().unwrap_or(vertex_indices[0])]
|
||||
};
|
||||
|
||||
let endpoint_coords = [
|
||||
layout_coords(&pts.elements()[endpoints[0]]),
|
||||
layout_coords(&pts.elements()[endpoints[1]]),
|
||||
];
|
||||
|
||||
wires.push(Wire {
|
||||
id: idx,
|
||||
label: format!("wire_{}", i),
|
||||
point: format_point(point),
|
||||
coords: layout_coords(point),
|
||||
endpoints,
|
||||
endpoint_coords,
|
||||
});
|
||||
}
|
||||
|
||||
// Build visible surfaces (geom_dim=2)
|
||||
let surface_indices = by_geom_dim.get(&2).map(|v| v.as_slice()).unwrap_or(&[]);
|
||||
|
||||
let mut surfaces: Vec<Surface> = Vec::new();
|
||||
for (i, &idx) in surface_indices.iter().enumerate() {
|
||||
let point = &pts.elements()[idx];
|
||||
|
||||
// Find boundary wires via DIRECT covering relations
|
||||
// Filter to only VISIBLE wires
|
||||
let mut boundary_wires: Vec<usize> = successors[idx]
|
||||
.iter()
|
||||
.chain(predecessors[idx].iter())
|
||||
.filter(|v| wire_set.contains(v))
|
||||
.copied()
|
||||
.collect();
|
||||
boundary_wires.sort();
|
||||
boundary_wires.dedup();
|
||||
|
||||
surfaces.push(Surface {
|
||||
id: idx,
|
||||
label: format!("surface_{}", i),
|
||||
point: format_point(point),
|
||||
coords: layout_coords(point),
|
||||
boundary_wires,
|
||||
});
|
||||
}
|
||||
|
||||
// Build output (no volumes - they're not rendered)
|
||||
let geometry = Geometry {
|
||||
metadata: Metadata {
|
||||
source: "half_braid.json".to_string(),
|
||||
dimension: n,
|
||||
total_points: pts.len(),
|
||||
visible_points: visible_indices.len(),
|
||||
total_covers: pts.covers().len(),
|
||||
},
|
||||
vertices,
|
||||
wires,
|
||||
surfaces,
|
||||
};
|
||||
|
||||
// Output JSON
|
||||
let json_output = serde_json::to_string_pretty(&geometry).expect("Failed to serialize");
|
||||
|
||||
// Write to file
|
||||
fs::write("fixtures/half_braid_geometry.json", &json_output)
|
||||
.expect("Failed to write fixtures/half_braid_geometry.json");
|
||||
|
||||
eprintln!("\nWrote fixtures/half_braid_geometry.json (VISIBLE ONLY)");
|
||||
eprintln!(" {} vertices", geometry.vertices.len());
|
||||
eprintln!(" {} wires", geometry.wires.len());
|
||||
eprintln!(" {} surfaces", geometry.surfaces.len());
|
||||
|
||||
// Also print to stdout for piping
|
||||
println!("{}", json_output);
|
||||
}
|
||||
|
|
@ -1,330 +0,0 @@
|
|||
//! 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!("════════════════════════════════════════════════════════════════════════════════");
|
||||
}
|
||||
|
|
@ -1,117 +0,0 @@
|
|||
//! Trace the merge topology of half_braid
|
||||
//!
|
||||
//! Run with: cargo run --example trace_merge
|
||||
|
||||
use std::fs;
|
||||
use zigzag_engine::diagram::Diagram;
|
||||
use zigzag_engine::import::load_homotopy_diagram_n;
|
||||
|
||||
fn main() {
|
||||
let json = fs::read_to_string("fixtures/half_braid.json")
|
||||
.expect("Failed to read half_braid.json");
|
||||
let half_braid = load_homotopy_diagram_n(&json)
|
||||
.expect("Failed to parse");
|
||||
|
||||
println!("=== MERGE TOPOLOGY ANALYSIS ===\n");
|
||||
|
||||
// Source 2-diagram structure
|
||||
if let Diagram::DiagramN(src) = half_braid.source() {
|
||||
println!("SOURCE 2-diagram ({} cospans):", src.cospans.len());
|
||||
println!("Heights: r0, s0, r1, s1, r2");
|
||||
println!();
|
||||
|
||||
// Print y-coordinates for each height
|
||||
println!("Height mappings (using layout_coords logic):");
|
||||
for i in 0..=src.cospans.len() {
|
||||
let y = (i as f64) - 1.0;
|
||||
println!(" r{}: y = {:.1}", i, y);
|
||||
if i < src.cospans.len() {
|
||||
let y_sing = i as f64;
|
||||
println!(" s{}: y = {:.1} ← SCALAR HERE", i, y_sing);
|
||||
}
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
// Target 2-diagram structure
|
||||
let target = half_braid.target();
|
||||
if let Diagram::DiagramN(tgt) = &target {
|
||||
println!("TARGET 2-diagram ({} cospans):", tgt.cospans.len());
|
||||
println!("Heights: r0, s0, r1");
|
||||
println!();
|
||||
|
||||
println!("Height mappings:");
|
||||
for i in 0..=tgt.cospans.len() {
|
||||
let y = (i as f64) - 1.0;
|
||||
println!(" r{}: y = {:.1}", i, y);
|
||||
if i < tgt.cospans.len() {
|
||||
let y_sing = i as f64;
|
||||
println!(" s{}: y = {:.1} ← MERGED SCALAR HERE", i, y_sing);
|
||||
}
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
println!("=== VISIBLE ELEMENT ANALYSIS ===\n");
|
||||
|
||||
println!("The 2 VERTICES (geom_dim=0) are the TWO INPUT SCALARS:");
|
||||
println!(" vertex (s0,s0,s0): z=-0.5, the FIRST scalar from source s0");
|
||||
println!(" vertex (s1,s0,s0): z=+0.5, the SECOND scalar from source s1");
|
||||
println!();
|
||||
|
||||
println!("The 3 WIRES (geom_dim=1) are the BOUNDARIES between regions:");
|
||||
println!(" wire (r0,s0,s0): z=-1.0, LEFT boundary (below both scalars)");
|
||||
println!(" wire (r1,s0,s0): z= 0.0, MIDDLE boundary (between the two scalars)");
|
||||
println!(" wire (r2,s0,s0): z=+1.0, RIGHT boundary (above both scalars)");
|
||||
println!();
|
||||
|
||||
println!("=== Y-SHAPE TOPOLOGY ===\n");
|
||||
|
||||
println!("The MERGE contracts source heights r0,s0,r1,s1,r2 into target heights r0,s0,r1");
|
||||
println!();
|
||||
println!("Mapping:");
|
||||
println!(" Source r0 (y=-1) → Target r0 (y=-1) [PRESERVED]");
|
||||
println!(" Source s0 (y= 0) → Target s0 (y= 0) [MERGED INTO]");
|
||||
println!(" Source r1 (y= 0) → Target s0 (y= 0) [ABSORBED]");
|
||||
println!(" Source s1 (y= 1) → Target s0 (y= 0) [MERGED INTO]");
|
||||
println!(" Source r2 (y=+1) → Target r1 (y= 0) [CONTRACTED DOWN]");
|
||||
println!();
|
||||
|
||||
println!("For the 3 visible wires:");
|
||||
println!();
|
||||
println!("Wire r0 (z=-1, LEFT EDGE):");
|
||||
println!(" Source endpoint (x=-1): y=-1 (at source height r0)");
|
||||
println!(" Merge waypoint (x= 0): y= 0 (at merge height s0)");
|
||||
println!(" Target endpoint (x=+1): y=-1 (at target height r0)");
|
||||
println!(" → This wire DIPS DOWN to the merge then back up");
|
||||
println!();
|
||||
|
||||
println!("Wire r1 (z=0, MIDDLE/STEM):");
|
||||
println!(" Source endpoint (x=-1): y= 0 (at source height r1, between s0 and s1)");
|
||||
println!(" Merge waypoint (x= 0): y= 0 (at merge height s0)");
|
||||
println!(" Target endpoint (x=+1): y= 0 (at target height s0)");
|
||||
println!(" → This is the STEM - stays at y=0 throughout");
|
||||
println!();
|
||||
|
||||
println!("Wire r2 (z=+1, RIGHT EDGE):");
|
||||
println!(" Source endpoint (x=-1): y=+1 (at source height r2)");
|
||||
println!(" Merge waypoint (x= 0): y= 0 (at merge height s0)");
|
||||
println!(" Target endpoint (x=+1): y= 0 (at target height r1)");
|
||||
println!(" → This wire comes DOWN from above into the merge");
|
||||
println!();
|
||||
|
||||
println!("=== THE Y-SHAPE ===\n");
|
||||
println!("Looking at y-z plane (height vs depth) at different x (time) slices:\n");
|
||||
|
||||
println!("At SOURCE (x=-1): At MERGE (x=0): At TARGET (x=+1):");
|
||||
println!(" ");
|
||||
println!("y=+1 ──●r2── y=+1 y=+1 ");
|
||||
println!(" │ ╲ ");
|
||||
println!(" │ ╲ ");
|
||||
println!("y= 0 ──●r1── ←s1 scalar y= 0 ●●● (merge) y= 0 ──●r1,r2── ");
|
||||
println!(" │ ╱ ↑ ↑ ");
|
||||
println!(" │ ╱ vertices merged ");
|
||||
println!("y=-1 ──●r0── ←s0 scalar y=-1 y=-1 ──●r0── ");
|
||||
println!(" ");
|
||||
println!(" z: -1 0 +1 -1 0 +1 -1 0 +1 ");
|
||||
}
|
||||
|
|
@ -1,115 +0,0 @@
|
|||
//! Trace scaffold nodes for visible wires through all time heights
|
||||
//!
|
||||
//! Run with: cargo run --example trace_scaffold
|
||||
|
||||
use std::fs;
|
||||
use zigzag_engine::diagram::Diagram;
|
||||
use zigzag_engine::import::load_homotopy_diagram_n;
|
||||
|
||||
fn main() {
|
||||
let json = fs::read_to_string("fixtures/half_braid.json")
|
||||
.expect("Failed to read half_braid.json");
|
||||
let half_braid = load_homotopy_diagram_n(&json)
|
||||
.expect("Failed to parse");
|
||||
|
||||
println!("=== SCAFFOLD NODE TRACING ===\n");
|
||||
|
||||
// Time structure of the 3-diagram
|
||||
println!("TIME STRUCTURE (coord[2]):");
|
||||
println!(" The half_braid has {} cospan(s)", half_braid.cospans.len());
|
||||
println!(" Time heights: r0 (source), s0 (merge), r1 (target)");
|
||||
println!();
|
||||
|
||||
// Source 2-diagram heights
|
||||
if let Diagram::DiagramN(src) = half_braid.source() {
|
||||
println!("SOURCE 2-DIAGRAM (at time r0):");
|
||||
println!(" {} cospans → heights: r0, s0, r1, s1, r2", src.cospans.len());
|
||||
println!(" Y-mapping: r0→-1, s0→0, r1→0, s1→1, r2→+1");
|
||||
println!();
|
||||
}
|
||||
|
||||
// Target 2-diagram heights
|
||||
let target = half_braid.target();
|
||||
if let Diagram::DiagramN(tgt) = &target {
|
||||
println!("TARGET 2-DIAGRAM (at time r1):");
|
||||
println!(" {} cospan(s) → heights: r0, s0, r1", tgt.cospans.len());
|
||||
println!(" Y-mapping: r0→-1, s0→0, r1→0");
|
||||
println!();
|
||||
}
|
||||
|
||||
println!("=== HEIGHT MAPPING THROUGH MERGE ===");
|
||||
println!();
|
||||
println!("The merge contracts source heights to target heights:");
|
||||
println!(" Source r0 → Target r0 (preserved, y stays at -1)");
|
||||
println!(" Source s0 → Target s0 (merges, y=0 → y=0)");
|
||||
println!(" Source r1 → Target s0 (absorbed into merge, y=0 → y=0)");
|
||||
println!(" Source s1 → Target s0 (merges, y=1 → y=0)");
|
||||
println!(" Source r2 → Target r1 (contracts down, y=+1 → y=0)");
|
||||
println!();
|
||||
|
||||
println!("=== SCAFFOLD NODE POSITIONS FOR EACH WIRE ===");
|
||||
println!();
|
||||
println!("Time positions: r0 at x=-1, s0 at x=0, r1 at x=+1");
|
||||
println!("Depth positions: r0→z=-1, r1→z=0, r2→z=+1");
|
||||
println!();
|
||||
|
||||
// Wire r0
|
||||
println!("WIRE r0 (coord[0]=r0, depth z=-1):");
|
||||
println!(" At time r0 (source): coord=(r0, r0, r0)");
|
||||
println!(" → coord[1]=r0=Regular(0) → y = -1");
|
||||
println!(" → position: (-1, -1, -1)");
|
||||
println!();
|
||||
println!(" At time s0 (merge): coord=(r0, s0, s0)");
|
||||
println!(" → coord[1]=s0=Singular(0) → y = 0");
|
||||
println!(" → position: (0, 0, -1)");
|
||||
println!();
|
||||
println!(" At time r1 (target): coord=(r0, r0, r1)");
|
||||
println!(" → coord[1]=r0=Regular(0) → y = -1");
|
||||
println!(" → position: (+1, -1, -1)");
|
||||
println!();
|
||||
println!(" Wire r0 polyline: [(-1,-1,-1), (0,0,-1), (+1,-1,-1)]");
|
||||
println!(" Shape: DIPS to merge, returns to original height");
|
||||
println!();
|
||||
|
||||
// Wire r1
|
||||
println!("WIRE r1 (coord[0]=r1, depth z=0):");
|
||||
println!(" At time r0 (source): coord=(r1, r1, r0)");
|
||||
println!(" → coord[1]=r1=Regular(1) → y = 0");
|
||||
println!(" → position: (-1, 0, 0)");
|
||||
println!();
|
||||
println!(" At time s0 (merge): coord=(r1, s0, s0)");
|
||||
println!(" → coord[1]=s0=Singular(0) → y = 0");
|
||||
println!(" → position: (0, 0, 0)");
|
||||
println!();
|
||||
println!(" At time r1 (target): coord=(r1, s0, r1)");
|
||||
println!(" → coord[1]=s0=Singular(0) → y = 0 (r1 absorbed into s0)");
|
||||
println!(" → position: (+1, 0, 0)");
|
||||
println!();
|
||||
println!(" Wire r1 polyline: [(-1,0,0), (0,0,0), (+1,0,0)]");
|
||||
println!(" Shape: FLAT at y=0 throughout - this is the STEM");
|
||||
println!();
|
||||
|
||||
// Wire r2
|
||||
println!("WIRE r2 (coord[0]=r2, depth z=+1):");
|
||||
println!(" At time r0 (source): coord=(r2, r2, r0)");
|
||||
println!(" → coord[1]=r2=Regular(2) → y = +1");
|
||||
println!(" → position: (-1, +1, +1)");
|
||||
println!();
|
||||
println!(" At time s0 (merge): coord=(r2, s0, s0)");
|
||||
println!(" → coord[1]=s0=Singular(0) → y = 0");
|
||||
println!(" → position: (0, 0, +1)");
|
||||
println!();
|
||||
println!(" At time r1 (target): coord=(r2, r1, r1)");
|
||||
println!(" → coord[1]=r1=Regular(1) → y = 0 (r2 contracted to r1)");
|
||||
println!(" → position: (+1, 0, +1)");
|
||||
println!();
|
||||
println!(" Wire r2 polyline: [(-1,+1,+1), (0,0,+1), (+1,0,+1)]");
|
||||
println!(" Shape: DROPS from y=+1 to y=0, stays at y=0");
|
||||
println!();
|
||||
|
||||
println!("=== SUMMARY ===");
|
||||
println!();
|
||||
println!("Wire r0: [(-1,-1,-1), (0,0,-1), (1,-1,-1)] // dips and returns");
|
||||
println!("Wire r1: [(-1, 0, 0), (0,0, 0), (1, 0, 0)] // flat stem");
|
||||
println!("Wire r2: [(-1,+1,+1), (0,0,+1), (1, 0,+1)] // drops and stays");
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,42 +0,0 @@
|
|||
warning: unnecessary parentheses around closure body
|
||||
--> homotopy-core/src/collapse.rs:135:22
|
||||
|
|
||||
135 | .filter(|&e| (e.target() != keep))
|
||||
| ^ ^
|
||||
|
|
||||
= note: `#[warn(unused_parens)]` (part of `#[warn(unused)]`) on by default
|
||||
help: remove these parentheses
|
||||
|
|
||||
135 - .filter(|&e| (e.target() != keep))
|
||||
135 + .filter(|&e| e.target() != keep)
|
||||
|
|
||||
|
||||
warning: unnecessary parentheses around closure body
|
||||
--> homotopy-core/src/collapse.rs:158:22
|
||||
|
|
||||
158 | .filter(|&e| (e.source() != keep))
|
||||
| ^ ^
|
||||
|
|
||||
help: remove these parentheses
|
||||
|
|
||||
158 - .filter(|&e| (e.source() != keep))
|
||||
158 + .filter(|&e| e.source() != keep)
|
||||
|
|
||||
|
||||
warning: `homotopy-core` (lib) generated 2 warnings (run `cargo fix --lib -p homotopy-core` to apply 2 suggestions)
|
||||
Compiling homotopy-core v0.1.0 (/home/maximus/.env/extern/diagrammatic-semiotics/homotopy-rs/homotopy-core)
|
||||
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.54s
|
||||
Running `target/debug/examples/export_dim456`
|
||||
=== Building dimension 4, 5, 6 test fixtures ===
|
||||
|
||||
lips: dim=4, size=1
|
||||
padded_4: dim=4, size=2
|
||||
as_5d: dim=5, size=1
|
||||
source dim=4, size=2
|
||||
as_6d: dim=6, size=1
|
||||
source dim=5, size=1
|
||||
|
||||
=== Exporting fixtures ===
|
||||
padded_4: 1076794 bytes
|
||||
as_5d: 1154944 bytes
|
||||
as_6d: 1233174 bytes
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
warning: unnecessary parentheses around closure body
|
||||
--> homotopy-core/src/collapse.rs:135:22
|
||||
|
|
||||
135 | .filter(|&e| (e.target() != keep))
|
||||
| ^ ^
|
||||
|
|
||||
= note: `#[warn(unused_parens)]` (part of `#[warn(unused)]`) on by default
|
||||
help: remove these parentheses
|
||||
|
|
||||
135 - .filter(|&e| (e.target() != keep))
|
||||
135 + .filter(|&e| e.target() != keep)
|
||||
|
|
||||
|
||||
warning: unnecessary parentheses around closure body
|
||||
--> homotopy-core/src/collapse.rs:158:22
|
||||
|
|
||||
158 | .filter(|&e| (e.source() != keep))
|
||||
| ^ ^
|
||||
|
|
||||
help: remove these parentheses
|
||||
|
|
||||
158 - .filter(|&e| (e.source() != keep))
|
||||
158 + .filter(|&e| e.source() != keep)
|
||||
|
|
||||
|
||||
warning: `homotopy-core` (lib) generated 2 warnings (run `cargo fix --lib -p homotopy-core` to apply 2 suggestions)
|
||||
Compiling homotopy-core v0.1.0 (/home/maximus/.env/extern/diagrammatic-semiotics/homotopy-rs/homotopy-core)
|
||||
warning: unused import: `homotopy_core::common::Boundary`
|
||||
--> homotopy-core/examples/export_dim4.rs:4:5
|
||||
|
|
||||
4 | use homotopy_core::common::Boundary;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default
|
||||
|
||||
warning: `homotopy-core` (example "export_dim4") generated 1 warning (run `cargo fix --example "export_dim4" -p homotopy-core` to apply 1 suggestion)
|
||||
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.48s
|
||||
Running `target/debug/examples/export_dim4`
|
||||
=== Searching for dimension 4 examples ===
|
||||
|
||||
--- lips() ---
|
||||
lips: dim=4, size=1
|
||||
source dim=3
|
||||
|
||||
--- pants_unit() ---
|
||||
pants_unit: dim=4, size=1
|
||||
source dim=3
|
||||
|
||||
--- algebraic_snake() ---
|
||||
algebraic_snake: dim=3, size=1
|
||||
|
||||
=== Approach 1: Padded half_braid lifted to dim 4 ===
|
||||
half_braid: dim=3, size=1
|
||||
padded_3d: dim=3, size=2
|
||||
padded_4d: dim=4, size=0
|
||||
|
||||
=== Approach 2: Pad lips() at dim 4 ===
|
||||
padded_lips: dim=4, size=2
|
||||
(original lips size was 1, now 2)
|
||||
|
||||
=== Exporting padded_lips (dim 4 with identity cospan) ===
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,41 +0,0 @@
|
|||
warning: unnecessary parentheses around closure body
|
||||
--> homotopy-core/src/collapse.rs:135:22
|
||||
|
|
||||
135 | .filter(|&e| (e.target() != keep))
|
||||
| ^ ^
|
||||
|
|
||||
= note: `#[warn(unused_parens)]` (part of `#[warn(unused)]`) on by default
|
||||
help: remove these parentheses
|
||||
|
|
||||
135 - .filter(|&e| (e.target() != keep))
|
||||
135 + .filter(|&e| e.target() != keep)
|
||||
|
|
||||
|
||||
warning: unnecessary parentheses around closure body
|
||||
--> homotopy-core/src/collapse.rs:158:22
|
||||
|
|
||||
158 | .filter(|&e| (e.source() != keep))
|
||||
| ^ ^
|
||||
|
|
||||
help: remove these parentheses
|
||||
|
|
||||
158 - .filter(|&e| (e.source() != keep))
|
||||
158 + .filter(|&e| e.source() != keep)
|
||||
|
|
||||
|
||||
warning: `homotopy-core` (lib) generated 2 warnings (run `cargo fix --lib -p homotopy-core` to apply 2 suggestions)
|
||||
Compiling homotopy-core v0.1.0 (/home/maximus/.env/extern/diagrammatic-semiotics/homotopy-rs/homotopy-core)
|
||||
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.52s
|
||||
Running `target/debug/examples/export_essential`
|
||||
=== Building essential identity scenario ===
|
||||
|
||||
two_scalars: dim=2, size=2
|
||||
two_scalars_3d: dim=3, size=0
|
||||
|
||||
Attempting contraction...
|
||||
Contraction failed: OutOfBounds
|
||||
|
||||
Fallback: using half_braid
|
||||
half_braid: dim=3, size=1
|
||||
padded_3d: dim=3, size=2
|
||||
wrapped_4d: dim=4, size=1
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,139 +0,0 @@
|
|||
{
|
||||
"metadata": {
|
||||
"source": "half_braid.json",
|
||||
"dimension": 3,
|
||||
"total_points": 23,
|
||||
"visible_points": 12,
|
||||
"total_covers": 35
|
||||
},
|
||||
"vertices": [
|
||||
{
|
||||
"id": 21,
|
||||
"label": "vertex_0",
|
||||
"point": "s0,s0,s0",
|
||||
"coords": [
|
||||
0.0,
|
||||
0.0,
|
||||
-0.5
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"label": "vertex_1",
|
||||
"point": "s1,s0,s0",
|
||||
"coords": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.5
|
||||
]
|
||||
}
|
||||
],
|
||||
"wires": [
|
||||
{
|
||||
"id": 18,
|
||||
"label": "wire_0",
|
||||
"point": "r0,s0,s0",
|
||||
"coords": [
|
||||
0.0,
|
||||
0.0,
|
||||
-1.0
|
||||
],
|
||||
"endpoints": [
|
||||
21,
|
||||
22
|
||||
],
|
||||
"endpoint_coords": [
|
||||
[
|
||||
0.0,
|
||||
0.0,
|
||||
-0.5
|
||||
],
|
||||
[
|
||||
0.0,
|
||||
0.0,
|
||||
0.5
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"label": "wire_1",
|
||||
"point": "r1,s0,s0",
|
||||
"coords": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"endpoints": [
|
||||
21,
|
||||
22
|
||||
],
|
||||
"endpoint_coords": [
|
||||
[
|
||||
0.0,
|
||||
0.0,
|
||||
-0.5
|
||||
],
|
||||
[
|
||||
0.0,
|
||||
0.0,
|
||||
0.5
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"label": "wire_2",
|
||||
"point": "r2,s0,s0",
|
||||
"coords": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
"endpoints": [
|
||||
21,
|
||||
22
|
||||
],
|
||||
"endpoint_coords": [
|
||||
[
|
||||
0.0,
|
||||
0.0,
|
||||
-0.5
|
||||
],
|
||||
[
|
||||
0.0,
|
||||
0.0,
|
||||
0.5
|
||||
]
|
||||
]
|
||||
}
|
||||
],
|
||||
"surfaces": [
|
||||
{
|
||||
"id": 16,
|
||||
"label": "surface_0",
|
||||
"point": "r0,r0,s0",
|
||||
"coords": [
|
||||
0.0,
|
||||
-1.0,
|
||||
-1.0
|
||||
],
|
||||
"boundary_wires": [
|
||||
18
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"label": "surface_1",
|
||||
"point": "r0,r1,s0",
|
||||
"coords": [
|
||||
0.0,
|
||||
0.0,
|
||||
-1.0
|
||||
],
|
||||
"boundary_wires": [
|
||||
18
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,464 +0,0 @@
|
|||
{
|
||||
"padded_3d": {
|
||||
"source": {
|
||||
"DiagramN": {
|
||||
"source": {
|
||||
"DiagramN": {
|
||||
"source": {
|
||||
"Diagram0": {
|
||||
"generator": {
|
||||
"id": 0,
|
||||
"dimension": 0
|
||||
},
|
||||
"orientation": "Positive"
|
||||
}
|
||||
},
|
||||
"cospans": []
|
||||
}
|
||||
},
|
||||
"cospans": [
|
||||
{
|
||||
"forward": {
|
||||
"RewriteN": {
|
||||
"dimension": 1,
|
||||
"cones": [
|
||||
{
|
||||
"index": 0,
|
||||
"internal": {
|
||||
"source": [],
|
||||
"target": {
|
||||
"forward": {
|
||||
"Rewrite0": [
|
||||
{
|
||||
"generator": {
|
||||
"id": 0,
|
||||
"dimension": 0
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
{
|
||||
"generator": {
|
||||
"id": 1,
|
||||
"dimension": 2
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
[
|
||||
[
|
||||
"Source",
|
||||
1
|
||||
],
|
||||
[
|
||||
[]
|
||||
]
|
||||
]
|
||||
]
|
||||
},
|
||||
"backward": {
|
||||
"Rewrite0": [
|
||||
{
|
||||
"generator": {
|
||||
"id": 0,
|
||||
"dimension": 0
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
{
|
||||
"generator": {
|
||||
"id": 1,
|
||||
"dimension": 2
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
[
|
||||
[
|
||||
"Target",
|
||||
1
|
||||
],
|
||||
[
|
||||
[]
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"regular_slices": [
|
||||
{
|
||||
"Rewrite0": [
|
||||
{
|
||||
"generator": {
|
||||
"id": 0,
|
||||
"dimension": 0
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
{
|
||||
"generator": {
|
||||
"id": 1,
|
||||
"dimension": 2
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
[
|
||||
[
|
||||
"Source",
|
||||
0
|
||||
],
|
||||
[
|
||||
[
|
||||
{
|
||||
"Regular": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
],
|
||||
"singular_slices": []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"backward": {
|
||||
"RewriteN": {
|
||||
"dimension": 1,
|
||||
"cones": [
|
||||
{
|
||||
"index": 0,
|
||||
"internal": {
|
||||
"source": [],
|
||||
"target": {
|
||||
"forward": {
|
||||
"Rewrite0": [
|
||||
{
|
||||
"generator": {
|
||||
"id": 0,
|
||||
"dimension": 0
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
{
|
||||
"generator": {
|
||||
"id": 1,
|
||||
"dimension": 2
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
[
|
||||
[
|
||||
"Source",
|
||||
1
|
||||
],
|
||||
[
|
||||
[]
|
||||
]
|
||||
]
|
||||
]
|
||||
},
|
||||
"backward": {
|
||||
"Rewrite0": [
|
||||
{
|
||||
"generator": {
|
||||
"id": 0,
|
||||
"dimension": 0
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
{
|
||||
"generator": {
|
||||
"id": 1,
|
||||
"dimension": 2
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
[
|
||||
[
|
||||
"Target",
|
||||
1
|
||||
],
|
||||
[
|
||||
[]
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"regular_slices": [
|
||||
{
|
||||
"Rewrite0": [
|
||||
{
|
||||
"generator": {
|
||||
"id": 0,
|
||||
"dimension": 0
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
{
|
||||
"generator": {
|
||||
"id": 1,
|
||||
"dimension": 2
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
[
|
||||
[
|
||||
"Target",
|
||||
0
|
||||
],
|
||||
[
|
||||
[
|
||||
{
|
||||
"Regular": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
],
|
||||
"singular_slices": []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"cospans": []
|
||||
},
|
||||
"scalar_3d": {
|
||||
"source": {
|
||||
"DiagramN": {
|
||||
"source": {
|
||||
"DiagramN": {
|
||||
"source": {
|
||||
"Diagram0": {
|
||||
"generator": {
|
||||
"id": 0,
|
||||
"dimension": 0
|
||||
},
|
||||
"orientation": "Positive"
|
||||
}
|
||||
},
|
||||
"cospans": []
|
||||
}
|
||||
},
|
||||
"cospans": [
|
||||
{
|
||||
"forward": {
|
||||
"RewriteN": {
|
||||
"dimension": 1,
|
||||
"cones": [
|
||||
{
|
||||
"index": 0,
|
||||
"internal": {
|
||||
"source": [],
|
||||
"target": {
|
||||
"forward": {
|
||||
"Rewrite0": [
|
||||
{
|
||||
"generator": {
|
||||
"id": 0,
|
||||
"dimension": 0
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
{
|
||||
"generator": {
|
||||
"id": 1,
|
||||
"dimension": 2
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
[
|
||||
[
|
||||
"Source",
|
||||
1
|
||||
],
|
||||
[
|
||||
[]
|
||||
]
|
||||
]
|
||||
]
|
||||
},
|
||||
"backward": {
|
||||
"Rewrite0": [
|
||||
{
|
||||
"generator": {
|
||||
"id": 0,
|
||||
"dimension": 0
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
{
|
||||
"generator": {
|
||||
"id": 1,
|
||||
"dimension": 2
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
[
|
||||
[
|
||||
"Target",
|
||||
1
|
||||
],
|
||||
[
|
||||
[]
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"regular_slices": [
|
||||
{
|
||||
"Rewrite0": [
|
||||
{
|
||||
"generator": {
|
||||
"id": 0,
|
||||
"dimension": 0
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
{
|
||||
"generator": {
|
||||
"id": 1,
|
||||
"dimension": 2
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
[
|
||||
[
|
||||
"Source",
|
||||
0
|
||||
],
|
||||
[
|
||||
[
|
||||
{
|
||||
"Regular": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
],
|
||||
"singular_slices": []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"backward": {
|
||||
"RewriteN": {
|
||||
"dimension": 1,
|
||||
"cones": [
|
||||
{
|
||||
"index": 0,
|
||||
"internal": {
|
||||
"source": [],
|
||||
"target": {
|
||||
"forward": {
|
||||
"Rewrite0": [
|
||||
{
|
||||
"generator": {
|
||||
"id": 0,
|
||||
"dimension": 0
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
{
|
||||
"generator": {
|
||||
"id": 1,
|
||||
"dimension": 2
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
[
|
||||
[
|
||||
"Source",
|
||||
1
|
||||
],
|
||||
[
|
||||
[]
|
||||
]
|
||||
]
|
||||
]
|
||||
},
|
||||
"backward": {
|
||||
"Rewrite0": [
|
||||
{
|
||||
"generator": {
|
||||
"id": 0,
|
||||
"dimension": 0
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
{
|
||||
"generator": {
|
||||
"id": 1,
|
||||
"dimension": 2
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
[
|
||||
[
|
||||
"Target",
|
||||
1
|
||||
],
|
||||
[
|
||||
[]
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"regular_slices": [
|
||||
{
|
||||
"Rewrite0": [
|
||||
{
|
||||
"generator": {
|
||||
"id": 0,
|
||||
"dimension": 0
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
{
|
||||
"generator": {
|
||||
"id": 1,
|
||||
"dimension": 2
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
[
|
||||
[
|
||||
"Target",
|
||||
0
|
||||
],
|
||||
[
|
||||
[
|
||||
{
|
||||
"Regular": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
],
|
||||
"singular_slices": []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"cospans": []
|
||||
}
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
warning: unnecessary parentheses around closure body
|
||||
--> homotopy-core/src/collapse.rs:135:22
|
||||
|
|
||||
135 | .filter(|&e| (e.target() != keep))
|
||||
| ^ ^
|
||||
|
|
||||
= note: `#[warn(unused_parens)]` (part of `#[warn(unused)]`) on by default
|
||||
help: remove these parentheses
|
||||
|
|
||||
135 - .filter(|&e| (e.target() != keep))
|
||||
135 + .filter(|&e| e.target() != keep)
|
||||
|
|
||||
|
||||
warning: unnecessary parentheses around closure body
|
||||
--> homotopy-core/src/collapse.rs:158:22
|
||||
|
|
||||
158 | .filter(|&e| (e.source() != keep))
|
||||
| ^ ^
|
||||
|
|
||||
help: remove these parentheses
|
||||
|
|
||||
158 - .filter(|&e| (e.source() != keep))
|
||||
158 + .filter(|&e| e.source() != keep)
|
||||
|
|
||||
|
||||
warning: `homotopy-core` (lib) generated 2 warnings (run `cargo fix --lib -p homotopy-core` to apply 2 suggestions)
|
||||
Compiling homotopy-core v0.1.0 (/home/maximus/.env/extern/diagrammatic-semiotics/homotopy-rs/homotopy-core)
|
||||
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.74s
|
||||
Running `target/debug/examples/export_padded`
|
||||
=== Original half_braid ===
|
||||
dimension: 3
|
||||
size: 1
|
||||
|
||||
=== Identity cospan ===
|
||||
forward is_identity: true
|
||||
backward is_identity: true
|
||||
cospan is_identity: true
|
||||
|
||||
=== Padded diagram ===
|
||||
dimension: 3
|
||||
size: 2
|
||||
last cospan is_identity: true
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,226 +0,0 @@
|
|||
{
|
||||
"source": {
|
||||
"DiagramN": {
|
||||
"source": {
|
||||
"Diagram0": {
|
||||
"generator": {
|
||||
"id": 0,
|
||||
"dimension": 0
|
||||
},
|
||||
"orientation": "Positive"
|
||||
}
|
||||
},
|
||||
"cospans": []
|
||||
}
|
||||
},
|
||||
"cospans": [
|
||||
{
|
||||
"forward": {
|
||||
"RewriteN": {
|
||||
"dimension": 1,
|
||||
"cones": [
|
||||
{
|
||||
"index": 0,
|
||||
"internal": {
|
||||
"source": [],
|
||||
"target": {
|
||||
"forward": {
|
||||
"Rewrite0": [
|
||||
{
|
||||
"generator": {
|
||||
"id": 0,
|
||||
"dimension": 0
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
{
|
||||
"generator": {
|
||||
"id": 1,
|
||||
"dimension": 2
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
[
|
||||
[
|
||||
"Source",
|
||||
1
|
||||
],
|
||||
[
|
||||
[]
|
||||
]
|
||||
]
|
||||
]
|
||||
},
|
||||
"backward": {
|
||||
"Rewrite0": [
|
||||
{
|
||||
"generator": {
|
||||
"id": 0,
|
||||
"dimension": 0
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
{
|
||||
"generator": {
|
||||
"id": 1,
|
||||
"dimension": 2
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
[
|
||||
[
|
||||
"Target",
|
||||
1
|
||||
],
|
||||
[
|
||||
[]
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"regular_slices": [
|
||||
{
|
||||
"Rewrite0": [
|
||||
{
|
||||
"generator": {
|
||||
"id": 0,
|
||||
"dimension": 0
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
{
|
||||
"generator": {
|
||||
"id": 1,
|
||||
"dimension": 2
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
[
|
||||
[
|
||||
"Source",
|
||||
0
|
||||
],
|
||||
[
|
||||
[
|
||||
{
|
||||
"Regular": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
],
|
||||
"singular_slices": []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"backward": {
|
||||
"RewriteN": {
|
||||
"dimension": 1,
|
||||
"cones": [
|
||||
{
|
||||
"index": 0,
|
||||
"internal": {
|
||||
"source": [],
|
||||
"target": {
|
||||
"forward": {
|
||||
"Rewrite0": [
|
||||
{
|
||||
"generator": {
|
||||
"id": 0,
|
||||
"dimension": 0
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
{
|
||||
"generator": {
|
||||
"id": 1,
|
||||
"dimension": 2
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
[
|
||||
[
|
||||
"Source",
|
||||
1
|
||||
],
|
||||
[
|
||||
[]
|
||||
]
|
||||
]
|
||||
]
|
||||
},
|
||||
"backward": {
|
||||
"Rewrite0": [
|
||||
{
|
||||
"generator": {
|
||||
"id": 0,
|
||||
"dimension": 0
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
{
|
||||
"generator": {
|
||||
"id": 1,
|
||||
"dimension": 2
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
[
|
||||
[
|
||||
"Target",
|
||||
1
|
||||
],
|
||||
[
|
||||
[]
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"regular_slices": [
|
||||
{
|
||||
"Rewrite0": [
|
||||
{
|
||||
"generator": {
|
||||
"id": 0,
|
||||
"dimension": 0
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
{
|
||||
"generator": {
|
||||
"id": 1,
|
||||
"dimension": 2
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
[
|
||||
[
|
||||
"Target",
|
||||
0
|
||||
],
|
||||
[
|
||||
[
|
||||
{
|
||||
"Regular": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
],
|
||||
"singular_slices": []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
warning: unnecessary parentheses around closure body
|
||||
--> homotopy-core/src/collapse.rs:135:22
|
||||
|
|
||||
135 | .filter(|&e| (e.target() != keep))
|
||||
| ^ ^
|
||||
|
|
||||
= note: `#[warn(unused_parens)]` (part of `#[warn(unused)]`) on by default
|
||||
help: remove these parentheses
|
||||
|
|
||||
135 - .filter(|&e| (e.target() != keep))
|
||||
135 + .filter(|&e| e.target() != keep)
|
||||
|
|
||||
|
||||
warning: unnecessary parentheses around closure body
|
||||
--> homotopy-core/src/collapse.rs:158:22
|
||||
|
|
||||
158 | .filter(|&e| (e.source() != keep))
|
||||
| ^ ^
|
||||
|
|
||||
help: remove these parentheses
|
||||
|
|
||||
158 - .filter(|&e| (e.source() != keep))
|
||||
158 + .filter(|&e| e.source() != keep)
|
||||
|
|
||||
|
||||
warning: `homotopy-core` (lib) generated 2 warnings (run `cargo fix --lib -p homotopy-core` to apply 2 suggestions)
|
||||
Compiling homotopy-core v0.1.0 (/home/maximus/.env/extern/diagrammatic-semiotics/homotopy-rs/homotopy-core)
|
||||
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.55s
|
||||
Running `target/debug/examples/scan_identities`
|
||||
=== Scanning homotopy-rs examples for identity cospans ===
|
||||
|
||||
--- Scanning lips() ---
|
||||
lips: dim=4, size=1
|
||||
|
||||
--- Scanning pants_unit() ---
|
||||
pants_unit: dim=4, size=1
|
||||
|
||||
--- Scanning touching() ---
|
||||
touching: dim=3, size=2
|
||||
|
||||
--- Scanning crossing() ---
|
||||
crossing: dim=3, size=2
|
||||
|
||||
--- Scanning algebraic_snake() ---
|
||||
algebraic_snake: dim=3, size=1
|
||||
|
||||
--- Scanning bubble() ---
|
||||
bubble: dim=2, size=2
|
||||
|
||||
--- Scanning snake() ---
|
||||
snake: dim=2, size=2
|
||||
|
||||
=== Constructing potential essential identity scenario ===
|
||||
|
||||
half_braid: dim=3, size=1
|
||||
half_braid.source: dim=2
|
||||
size=2
|
||||
|
||||
dim3_with_id: dim=3, size=2
|
||||
cospan[0] is_identity: false
|
||||
cospan[1] is_identity: true
|
||||
|
||||
Attempting contraction on dim3_with_id...
|
||||
Contraction succeeded!
|
||||
result: dim=4, size=1
|
||||
FOUND: source at depth=1, dim=3, size=2, identity_cospans=[1]/2, path=["source"]
|
||||
|
||||
=== Exporting contracted diagram ===
|
||||
|
|
@ -1,434 +0,0 @@
|
|||
{
|
||||
"source": {
|
||||
"DiagramN": {
|
||||
"source": {
|
||||
"Diagram0": {
|
||||
"generator": {
|
||||
"id": 0,
|
||||
"dimension": 0
|
||||
},
|
||||
"orientation": "Positive"
|
||||
}
|
||||
},
|
||||
"cospans": []
|
||||
}
|
||||
},
|
||||
"cospans": [
|
||||
{
|
||||
"forward": {
|
||||
"RewriteN": {
|
||||
"dimension": 1,
|
||||
"cones": [
|
||||
{
|
||||
"index": 0,
|
||||
"internal": {
|
||||
"source": [],
|
||||
"target": {
|
||||
"forward": {
|
||||
"Rewrite0": [
|
||||
{
|
||||
"generator": {
|
||||
"id": 0,
|
||||
"dimension": 0
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
{
|
||||
"generator": {
|
||||
"id": 1,
|
||||
"dimension": 2
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
[
|
||||
[
|
||||
"Source",
|
||||
1
|
||||
],
|
||||
[
|
||||
[]
|
||||
]
|
||||
]
|
||||
]
|
||||
},
|
||||
"backward": {
|
||||
"Rewrite0": [
|
||||
{
|
||||
"generator": {
|
||||
"id": 0,
|
||||
"dimension": 0
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
{
|
||||
"generator": {
|
||||
"id": 1,
|
||||
"dimension": 2
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
[
|
||||
[
|
||||
"Target",
|
||||
1
|
||||
],
|
||||
[
|
||||
[]
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"regular_slices": [
|
||||
{
|
||||
"Rewrite0": [
|
||||
{
|
||||
"generator": {
|
||||
"id": 0,
|
||||
"dimension": 0
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
{
|
||||
"generator": {
|
||||
"id": 1,
|
||||
"dimension": 2
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
[
|
||||
[
|
||||
"Source",
|
||||
0
|
||||
],
|
||||
[
|
||||
[
|
||||
{
|
||||
"Regular": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
],
|
||||
"singular_slices": []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"backward": {
|
||||
"RewriteN": {
|
||||
"dimension": 1,
|
||||
"cones": [
|
||||
{
|
||||
"index": 0,
|
||||
"internal": {
|
||||
"source": [],
|
||||
"target": {
|
||||
"forward": {
|
||||
"Rewrite0": [
|
||||
{
|
||||
"generator": {
|
||||
"id": 0,
|
||||
"dimension": 0
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
{
|
||||
"generator": {
|
||||
"id": 1,
|
||||
"dimension": 2
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
[
|
||||
[
|
||||
"Source",
|
||||
1
|
||||
],
|
||||
[
|
||||
[]
|
||||
]
|
||||
]
|
||||
]
|
||||
},
|
||||
"backward": {
|
||||
"Rewrite0": [
|
||||
{
|
||||
"generator": {
|
||||
"id": 0,
|
||||
"dimension": 0
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
{
|
||||
"generator": {
|
||||
"id": 1,
|
||||
"dimension": 2
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
[
|
||||
[
|
||||
"Target",
|
||||
1
|
||||
],
|
||||
[
|
||||
[]
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"regular_slices": [
|
||||
{
|
||||
"Rewrite0": [
|
||||
{
|
||||
"generator": {
|
||||
"id": 0,
|
||||
"dimension": 0
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
{
|
||||
"generator": {
|
||||
"id": 1,
|
||||
"dimension": 2
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
[
|
||||
[
|
||||
"Target",
|
||||
0
|
||||
],
|
||||
[
|
||||
[
|
||||
{
|
||||
"Regular": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
],
|
||||
"singular_slices": []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"forward": {
|
||||
"RewriteN": {
|
||||
"dimension": 1,
|
||||
"cones": [
|
||||
{
|
||||
"index": 0,
|
||||
"internal": {
|
||||
"source": [],
|
||||
"target": {
|
||||
"forward": {
|
||||
"Rewrite0": [
|
||||
{
|
||||
"generator": {
|
||||
"id": 0,
|
||||
"dimension": 0
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
{
|
||||
"generator": {
|
||||
"id": 2,
|
||||
"dimension": 2
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
[
|
||||
[
|
||||
"Source",
|
||||
1
|
||||
],
|
||||
[
|
||||
[]
|
||||
]
|
||||
]
|
||||
]
|
||||
},
|
||||
"backward": {
|
||||
"Rewrite0": [
|
||||
{
|
||||
"generator": {
|
||||
"id": 0,
|
||||
"dimension": 0
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
{
|
||||
"generator": {
|
||||
"id": 2,
|
||||
"dimension": 2
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
[
|
||||
[
|
||||
"Target",
|
||||
1
|
||||
],
|
||||
[
|
||||
[]
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"regular_slices": [
|
||||
{
|
||||
"Rewrite0": [
|
||||
{
|
||||
"generator": {
|
||||
"id": 0,
|
||||
"dimension": 0
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
{
|
||||
"generator": {
|
||||
"id": 2,
|
||||
"dimension": 2
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
[
|
||||
[
|
||||
"Source",
|
||||
0
|
||||
],
|
||||
[
|
||||
[
|
||||
{
|
||||
"Regular": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
],
|
||||
"singular_slices": []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"backward": {
|
||||
"RewriteN": {
|
||||
"dimension": 1,
|
||||
"cones": [
|
||||
{
|
||||
"index": 0,
|
||||
"internal": {
|
||||
"source": [],
|
||||
"target": {
|
||||
"forward": {
|
||||
"Rewrite0": [
|
||||
{
|
||||
"generator": {
|
||||
"id": 0,
|
||||
"dimension": 0
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
{
|
||||
"generator": {
|
||||
"id": 2,
|
||||
"dimension": 2
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
[
|
||||
[
|
||||
"Source",
|
||||
1
|
||||
],
|
||||
[
|
||||
[]
|
||||
]
|
||||
]
|
||||
]
|
||||
},
|
||||
"backward": {
|
||||
"Rewrite0": [
|
||||
{
|
||||
"generator": {
|
||||
"id": 0,
|
||||
"dimension": 0
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
{
|
||||
"generator": {
|
||||
"id": 2,
|
||||
"dimension": 2
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
[
|
||||
[
|
||||
"Target",
|
||||
1
|
||||
],
|
||||
[
|
||||
[]
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"regular_slices": [
|
||||
{
|
||||
"Rewrite0": [
|
||||
{
|
||||
"generator": {
|
||||
"id": 0,
|
||||
"dimension": 0
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
{
|
||||
"generator": {
|
||||
"id": 2,
|
||||
"dimension": 2
|
||||
},
|
||||
"orientation": "Positive"
|
||||
},
|
||||
[
|
||||
[
|
||||
"Target",
|
||||
0
|
||||
],
|
||||
[
|
||||
[
|
||||
{
|
||||
"Regular": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
],
|
||||
"singular_slices": []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
1197
src/degeneracy.rs
1197
src/degeneracy.rs
File diff suppressed because it is too large
Load diff
200
src/diagram.rs
200
src/diagram.rs
|
|
@ -166,12 +166,9 @@ impl Cospan {
|
|||
Self { forward, backward }
|
||||
}
|
||||
|
||||
/// Check if this is an identity cospan (both legs are trivially identity).
|
||||
///
|
||||
/// Uses `is_trivial()` which recognizes both `Rewrite::Identity` and
|
||||
/// `RewriteN` with empty cones (as serialized by homotopy-rs).
|
||||
/// Check if this is an identity cospan (both legs are isomorphisms).
|
||||
pub fn is_identity(&self) -> bool {
|
||||
self.forward.is_trivial() && self.backward.is_trivial()
|
||||
self.forward.is_identity() && self.backward.is_identity()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -192,32 +189,11 @@ pub enum Rewrite {
|
|||
}
|
||||
|
||||
impl Rewrite {
|
||||
/// Check if this is explicitly marked as an identity rewrite.
|
||||
///
|
||||
/// Returns true only for `Rewrite::Identity`. This is conservative and
|
||||
/// used for degeneracy tracking where we need to distinguish between
|
||||
/// a true identity and a parallel degeneracy with non-identity slices.
|
||||
/// Check if this is an identity rewrite.
|
||||
pub fn is_identity(&self) -> bool {
|
||||
matches!(self, Rewrite::Identity)
|
||||
}
|
||||
|
||||
/// Check if this rewrite is trivially identity (makes no changes).
|
||||
///
|
||||
/// Returns true for:
|
||||
/// - `Rewrite::Identity` (explicit identity marker)
|
||||
/// - `RewriteN` with empty cones (no structural changes at this level)
|
||||
/// - `Rewrite0` with source == target
|
||||
///
|
||||
/// This is used for detecting identity cospans that should be removed
|
||||
/// during normalisation.
|
||||
pub fn is_trivial(&self) -> bool {
|
||||
match self {
|
||||
Rewrite::Identity => true,
|
||||
Rewrite::RewriteN(r) => r.cones.is_empty(),
|
||||
Rewrite::Rewrite0 { source, target } => source == target,
|
||||
}
|
||||
}
|
||||
|
||||
/// The dimension of this rewrite.
|
||||
pub fn dimension(&self) -> usize {
|
||||
match self {
|
||||
|
|
@ -382,9 +358,7 @@ pub struct Cone {
|
|||
pub source: Vec<Cospan>,
|
||||
/// Target cospan (what the source contracts to)
|
||||
pub target: Cospan,
|
||||
/// Slice rewrites for each source singular height.
|
||||
/// Length equals source.len() (one per source cospan apex).
|
||||
/// Matches homotopy-rs singular_slices convention.
|
||||
/// Slice rewrites for each interior boundary
|
||||
pub slices: Vec<Rewrite>,
|
||||
}
|
||||
|
||||
|
|
@ -403,136 +377,6 @@ impl Cone {
|
|||
pub fn source_size(&self) -> usize {
|
||||
self.source.len()
|
||||
}
|
||||
|
||||
/// The number of singular slices in this cone.
|
||||
/// Same as source_size() - one singular slice per source cospan.
|
||||
pub fn len(&self) -> usize {
|
||||
self.source.len()
|
||||
}
|
||||
|
||||
/// Check if this cone is empty (no source cospans).
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.source.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl RewriteN {
|
||||
/// Compute where a regular height in the source maps to in the target.
|
||||
///
|
||||
/// For a rewrite f: A → B, given a regular height h in A,
|
||||
/// returns the corresponding regular height in B.
|
||||
///
|
||||
/// Based on homotopy-rs implementation.
|
||||
pub fn regular_image(&self, h: usize) -> usize {
|
||||
let mut height = h;
|
||||
for cone in &self.cones {
|
||||
// Only affect heights that are completely AFTER this cone's source range
|
||||
if height >= cone.index + cone.source_size() {
|
||||
// Shift down by the contraction: source_size cospans become 1
|
||||
height -= cone.source_size().saturating_sub(1);
|
||||
}
|
||||
}
|
||||
height
|
||||
}
|
||||
|
||||
/// Compute the preimage of a regular height from target back to source.
|
||||
///
|
||||
/// For a rewrite f: A → B, given a regular height h in B,
|
||||
/// returns the corresponding regular height in A.
|
||||
///
|
||||
/// This is the inverse of regular_image.
|
||||
pub fn regular_preimage(&self, target_height: usize) -> usize {
|
||||
let mut source_height = target_height;
|
||||
for cone in &self.cones {
|
||||
// For each cone that starts before or at this target height,
|
||||
// we need to account for the expansion
|
||||
if target_height > cone.index {
|
||||
source_height += cone.source_size().saturating_sub(1);
|
||||
}
|
||||
}
|
||||
source_height
|
||||
}
|
||||
|
||||
/// Compute the preimage of a singular height h in the target.
|
||||
///
|
||||
/// For a rewrite f: A → B, given a singular height h in B,
|
||||
/// returns all singular heights in A that map to h.
|
||||
///
|
||||
/// This handles three cases:
|
||||
/// 1. **Contraction**: A cone targets h and has len > 0. Returns all source
|
||||
/// heights consumed by that cone.
|
||||
/// 2. **Insertion**: A cone targets h but has len == 0. Returns empty (no
|
||||
/// source heights map to this insertion point).
|
||||
/// 3. **Passthrough**: No cone targets h. Returns the single source height
|
||||
/// that passes through to h (computed from the cone structure).
|
||||
pub fn singular_preimage(&self, target_h: usize) -> Vec<usize> {
|
||||
let mut current_source = 0;
|
||||
let mut current_target = 0;
|
||||
|
||||
for cone in &self.cones {
|
||||
// Handle passthroughs before this cone
|
||||
while current_target < cone.index {
|
||||
if current_target == target_h {
|
||||
// Found passthrough at target_h
|
||||
return vec![current_source];
|
||||
}
|
||||
current_source += 1;
|
||||
current_target += 1;
|
||||
}
|
||||
|
||||
// Handle the cone itself
|
||||
if current_target == target_h {
|
||||
// This cone targets our height
|
||||
// Return all source heights in the cone's range
|
||||
// (empty if len == 0, i.e., insertion)
|
||||
return (current_source..current_source + cone.len()).collect();
|
||||
}
|
||||
|
||||
current_source += cone.len();
|
||||
current_target += 1; // Cone produces 1 target cospan
|
||||
}
|
||||
|
||||
// Handle passthroughs after all cones
|
||||
// target_h is beyond all cone indices
|
||||
let offset = target_h - current_target;
|
||||
vec![current_source + offset]
|
||||
}
|
||||
|
||||
/// Get the target heights (where cones map to).
|
||||
pub fn targets(&self) -> impl Iterator<Item = usize> + '_ {
|
||||
self.cones.iter().map(|c| c.index)
|
||||
}
|
||||
|
||||
/// Get the slice rewrite at a source singular height.
|
||||
///
|
||||
/// For a rewrite f: A → B, given a source singular height h,
|
||||
/// returns the (n-1)-dimensional rewrite between source's singular
|
||||
/// slice at h and the corresponding target singular slice.
|
||||
///
|
||||
/// Based on homotopy-rs: finds the cone containing this source height,
|
||||
/// then indexes into its singular_slices.
|
||||
pub fn slice(&self, source_height: usize) -> Rewrite {
|
||||
// Find which cone contains this source height
|
||||
let mut source_offset = 0;
|
||||
for cone in &self.cones {
|
||||
let source_end = source_offset + cone.len();
|
||||
if source_height >= source_offset && source_height < source_end {
|
||||
// Found the cone - index into its slices
|
||||
let local_idx = source_height - source_offset;
|
||||
if local_idx < cone.slices.len() {
|
||||
return cone.slices[local_idx].clone();
|
||||
}
|
||||
}
|
||||
source_offset = source_end;
|
||||
}
|
||||
// Height is outside all cones (passthrough), return identity
|
||||
Rewrite::Identity
|
||||
}
|
||||
|
||||
/// Get the cone that targets a specific height, if any.
|
||||
pub fn cone_over_target(&self, target_height: usize) -> Option<&Cone> {
|
||||
self.cones.iter().find(|c| c.index == target_height)
|
||||
}
|
||||
}
|
||||
|
||||
// === Diagram methods ===
|
||||
|
|
@ -642,42 +486,6 @@ impl DiagramMap {
|
|||
pub fn is_identity(&self) -> bool {
|
||||
self.rewrite.is_identity()
|
||||
}
|
||||
|
||||
/// Compose two diagram maps: (g ∘ f) where self = f and other = g.
|
||||
///
|
||||
/// For identity maps, composition is trivial.
|
||||
/// For rewrites, we need to compose the underlying structure.
|
||||
pub fn compose(&self, other: &DiagramMap) -> DiagramMap {
|
||||
if self.is_identity() {
|
||||
other.clone()
|
||||
} else if other.is_identity() {
|
||||
self.clone()
|
||||
} else {
|
||||
// TODO: Implement full rewrite composition
|
||||
// For now, return other (this is a simplification)
|
||||
other.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if a singular height h is in the image of this map's singular component.
|
||||
///
|
||||
/// For degeneracy maps, this checks if height h would be preserved
|
||||
/// (i.e., is not an inserted identity cospan position).
|
||||
pub fn has_singular_height_in_image(&self, h: usize) -> bool {
|
||||
match &self.rewrite {
|
||||
Rewrite::Identity => true, // Identity maps preserve all heights
|
||||
Rewrite::Rewrite0 { .. } => true, // 0-dim has no singular structure
|
||||
Rewrite::RewriteN(r) => {
|
||||
// Check if h is NOT an insertion point (not in any cone's empty-source positions)
|
||||
let insertion_points: std::collections::HashSet<usize> = r.cones
|
||||
.iter()
|
||||
.filter(|c| c.source.is_empty())
|
||||
.map(|c| c.index)
|
||||
.collect();
|
||||
!insertion_points.contains(&h)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Direction for slice iteration.
|
||||
|
|
|
|||
1157
src/explosion.rs
1157
src/explosion.rs
File diff suppressed because it is too large
Load diff
1491
src/import.rs
1491
src/import.rs
File diff suppressed because it is too large
Load diff
|
|
@ -36,7 +36,6 @@ pub mod normalise;
|
|||
pub mod typecheck;
|
||||
pub mod explosion;
|
||||
pub mod layout;
|
||||
pub mod import;
|
||||
|
||||
// Re-exports for convenience
|
||||
pub use monotone::MonotoneMap;
|
||||
|
|
@ -45,5 +44,5 @@ pub use diagram::{Diagram, DiagramN, Cospan, Rewrite, Cone};
|
|||
pub use signature::{Generator, Signature};
|
||||
pub use normalise::{NormalisationResult, normalise, normalise_sink};
|
||||
pub use typecheck::{TypeError, type_check};
|
||||
pub use explosion::{Point, HeightLabel, Poset, InjectificationResult, injectify};
|
||||
pub use explosion::{Point, HeightLabel};
|
||||
pub use layout::{Layout, LayoutConstraints, SpringConstraint};
|
||||
|
|
|
|||
579
src/normalise.rs
579
src/normalise.rs
|
|
@ -4,14 +4,14 @@
|
|||
//! the poset of degeneracy subobjects of a diagram T. This removes all
|
||||
//! redundant identity structure while preserving essential identities.
|
||||
//!
|
||||
//! Key insight: In dimension >= 4, some identity cospans are ESSENTIAL -
|
||||
//! Key insight: In dimension ≥ 4, some identity cospans are ESSENTIAL —
|
||||
//! removing them would make zigzag maps ill-defined (no monotone function
|
||||
//! of the required type exists). The algorithm detects and preserves these.
|
||||
//!
|
||||
//! # Algorithm Overview (Construction 17)
|
||||
//!
|
||||
//! Input: A sink S = (T, {fi: Ai -> T})
|
||||
//! Output: Degeneracy d: N -> T and factorisations Ai -> N
|
||||
//! Input: A sink S = (T, {fᵢ: Aᵢ → T})
|
||||
//! Output: Degeneracy d: N → T and factorisations Aᵢ → N
|
||||
//!
|
||||
//! 1. Base case (dim 0): d = identity
|
||||
//! 2. Recursive case:
|
||||
|
|
@ -19,16 +19,16 @@
|
|||
//! b. Normalise at each singular height (recursive, including cospan legs)
|
||||
//! c. Assemble into zigzag P with parallel degeneracy dP
|
||||
//! d. Remove trivial cospans not in image of any sink map
|
||||
//! e. Compose: d = dP o dS
|
||||
//! e. Compose: d = dP ∘ dS
|
||||
|
||||
use crate::diagram::{Diagram, DiagramN, DiagramMap, Rewrite, Cospan, RewriteN, Cone};
|
||||
use crate::diagram::{Diagram, DiagramN, DiagramMap, Rewrite};
|
||||
|
||||
/// Result of normalising a diagram (or sink).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NormalisationResult {
|
||||
/// The normalised diagram N
|
||||
pub normal_form: Diagram,
|
||||
/// The degeneracy map d: N -> T
|
||||
/// The degeneracy map d: N → T
|
||||
pub degeneracy: DiagramMap,
|
||||
/// Factorisations of each sink map through the degeneracy
|
||||
pub factorisations: Vec<DiagramMap>,
|
||||
|
|
@ -61,11 +61,9 @@ impl<'a> Sink<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Proposition 19: Normalise a sink (Construction 17).
|
||||
/// Normalise a sink (Construction 17).
|
||||
///
|
||||
/// This is the core normalisation algorithm from the LICS 2022 paper.
|
||||
/// Correctness: The output degeneracy d: N -> T is the smallest element
|
||||
/// of Deg(T) through which all sink maps factor.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `sink` - The sink to normalise (target diagram + incoming maps)
|
||||
|
|
@ -73,8 +71,8 @@ impl<'a> Sink<'a> {
|
|||
/// # Returns
|
||||
/// A `NormalisationResult` containing:
|
||||
/// - The normal form N
|
||||
/// - The degeneracy d: N -> T
|
||||
/// - Factorisations Ai -> N for each sink map
|
||||
/// - The degeneracy d: N → T
|
||||
/// - Factorisations Aᵢ → N for each sink map
|
||||
pub fn normalise_sink(sink: &Sink) -> NormalisationResult {
|
||||
match sink.target {
|
||||
Diagram::Diagram0(_) => {
|
||||
|
|
@ -92,15 +90,13 @@ pub fn normalise_sink(sink: &Sink) -> NormalisationResult {
|
|||
}
|
||||
}
|
||||
|
||||
/// Construction 17: Normalise an n-dimensional diagram (n > 0).
|
||||
///
|
||||
/// Implements the full 5-step algorithm for dimension > 0.
|
||||
/// Normalise an n-dimensional diagram (n > 0).
|
||||
fn normalise_sink_n(target: &DiagramN, sink_maps: &[DiagramMap]) -> NormalisationResult {
|
||||
// Step 1: Normalise at each regular height
|
||||
let regular_normalisations = normalise_regular_heights(target, sink_maps);
|
||||
|
||||
// Step 2: Normalise at each singular height
|
||||
// CRITICAL: Include P(rh) -> T(rh) -> T(sh) composites in each sink
|
||||
// CRITICAL: Include P(rₕ) → T(rₕ) → T(sₕ) composites in each sink
|
||||
let singular_normalisations = normalise_singular_heights(
|
||||
target,
|
||||
sink_maps,
|
||||
|
|
@ -112,7 +108,6 @@ fn normalise_sink_n(target: &DiagramN, sink_maps: &[DiagramMap]) -> Normalisatio
|
|||
target,
|
||||
®ular_normalisations,
|
||||
&singular_normalisations,
|
||||
sink_maps,
|
||||
);
|
||||
|
||||
// Step 4: Remove trivial cospans not in image of any sink map
|
||||
|
|
@ -124,8 +119,8 @@ fn normalise_sink_n(target: &DiagramN, sink_maps: &[DiagramMap]) -> Normalisatio
|
|||
&assembled_factorisations,
|
||||
);
|
||||
|
||||
// Step 5: Compose degeneracies d = dP o dS
|
||||
let degeneracy = compose_degeneracies(&d_simple, &d_parallel);
|
||||
// Step 5: Compose degeneracies d = dP ∘ dS
|
||||
let degeneracy = compose_degeneracies(&d_parallel, &d_simple);
|
||||
|
||||
NormalisationResult {
|
||||
normal_form: n,
|
||||
|
|
@ -135,7 +130,6 @@ fn normalise_sink_n(target: &DiagramN, sink_maps: &[DiagramMap]) -> Normalisatio
|
|||
}
|
||||
|
||||
/// Intermediate result for regular height normalisation.
|
||||
#[derive(Debug, Clone)]
|
||||
struct RegularNormalisation {
|
||||
/// Normalised diagram at this regular height
|
||||
normal_form: Diagram,
|
||||
|
|
@ -145,12 +139,7 @@ struct RegularNormalisation {
|
|||
factorisations: Vec<DiagramMap>,
|
||||
}
|
||||
|
||||
/// Construction 17, Step 1: Normalise at each regular height.
|
||||
///
|
||||
/// For each regular height rh:
|
||||
/// - Extract the slice T(rh)
|
||||
/// - Collect sink maps restricted to this height: fi(rh): Ai(r_{fi^r(h)}) -> T(rh)
|
||||
/// - Recursively normalise
|
||||
/// Normalise at each regular height of the diagram.
|
||||
fn normalise_regular_heights(
|
||||
target: &DiagramN,
|
||||
sink_maps: &[DiagramMap],
|
||||
|
|
@ -159,24 +148,20 @@ fn normalise_regular_heights(
|
|||
let mut results = Vec::with_capacity(num_regular);
|
||||
|
||||
for h in 0..num_regular {
|
||||
// Get the regular slice T(rh)
|
||||
// Get the regular slice T(rₕ)
|
||||
let t_r_h = target.regular_slice(h).unwrap_or_else(|| {
|
||||
// Fallback to source if slice computation not available
|
||||
// Fallback to source if slice computation not implemented
|
||||
(*target.source).clone()
|
||||
});
|
||||
|
||||
// Collect sink maps restricted to this regular height
|
||||
// Each fi(rh): Ai(r_{fi^r(h)}) -> T(rh)
|
||||
// The regular map fi^r is derived from the singular map via Wraith's R
|
||||
// Each fᵢ(rₕ): Aᵢ(r_{fᵢʳ(h)}) → T(rₕ)
|
||||
let restricted_maps: Vec<DiagramMap> = sink_maps
|
||||
.iter()
|
||||
.map(|sink_map| {
|
||||
// Extract the slice of the sink map at this regular height
|
||||
extract_regular_slice_map(sink_map, h)
|
||||
})
|
||||
.map(|_| DiagramMap::identity(&t_r_h))
|
||||
.collect();
|
||||
|
||||
// Recursively normalise this lower-dimensional sink
|
||||
// Recursively normalise
|
||||
let sub_sink = Sink::new(&t_r_h, restricted_maps);
|
||||
let sub_result = normalise_sink(&sub_sink);
|
||||
|
||||
|
|
@ -190,48 +175,25 @@ fn normalise_regular_heights(
|
|||
results
|
||||
}
|
||||
|
||||
/// Helper for Construction 17, Step 1: Extract the regular slice map at height h.
|
||||
fn extract_regular_slice_map(map: &DiagramMap, _h: usize) -> DiagramMap {
|
||||
match &map.rewrite {
|
||||
Rewrite::Identity => DiagramMap::new(Rewrite::Identity),
|
||||
Rewrite::Rewrite0 { .. } => map.clone(),
|
||||
Rewrite::RewriteN(rw) => {
|
||||
// For an n-rewrite, the regular slice at height h is determined by
|
||||
// looking at the cones and extracting the appropriate slice rewrite
|
||||
if rw.cones.is_empty() {
|
||||
DiagramMap::new(Rewrite::Identity)
|
||||
} else {
|
||||
// Find the slice data for this height
|
||||
// This would normally involve looking at cone boundaries
|
||||
DiagramMap::new(Rewrite::Identity)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Intermediate result for singular height normalisation.
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(dead_code)]
|
||||
struct SingularNormalisation {
|
||||
/// Normalised diagram at this singular height
|
||||
normal_form: Diagram,
|
||||
/// Degeneracy from normal form to original
|
||||
degeneracy: DiagramMap,
|
||||
/// Forward cospan leg from left regular (P(rh) -> P(sh))
|
||||
/// Forward cospan leg from left regular
|
||||
forward_leg: DiagramMap,
|
||||
/// Backward cospan leg from right regular (P(r{h+1}) -> P(sh))
|
||||
/// Backward cospan leg from right regular
|
||||
backward_leg: DiagramMap,
|
||||
/// Factorisations for each sink map at this height
|
||||
factorisations: Vec<DiagramMap>,
|
||||
}
|
||||
|
||||
/// Construction 17, Step 2: Normalise at each singular height (with cospan legs in sink).
|
||||
/// Normalise at each singular height of the diagram.
|
||||
///
|
||||
/// CRITICAL: The sink at each singular height includes:
|
||||
/// - Direct singular maps from sink: fi(st) for t in (fi^s)^{-1}(h)
|
||||
/// - Cospan legs: P(rh) -> T(rh) -> T(sh) and P(r{h+1}) -> T(r{h+1}) -> T(sh)
|
||||
///
|
||||
/// The cospan leg composites are essential for preserving the zigzag structure.
|
||||
/// - Direct singular maps from sink: fᵢ(sₜ) for t ∈ (fᵢˢ)⁻¹(h)
|
||||
/// - Cospan legs: P(rₕ) → T(rₕ) → T(sₕ) and P(rₕ₊₁) → T(rₕ₊₁) → T(sₕ)
|
||||
fn normalise_singular_heights(
|
||||
target: &DiagramN,
|
||||
sink_maps: &[DiagramMap],
|
||||
|
|
@ -241,151 +203,71 @@ fn normalise_singular_heights(
|
|||
let mut results = Vec::with_capacity(num_singular);
|
||||
|
||||
for h in 0..num_singular {
|
||||
// Get the singular slice T(sh)
|
||||
// Get the singular slice T(sₕ)
|
||||
let t_s_h = target.singular_slice(h).unwrap_or_else(|| {
|
||||
// Fallback to source if slice computation not available
|
||||
// Fallback if slice computation not implemented
|
||||
(*target.source).clone()
|
||||
});
|
||||
|
||||
// Build the sink for this singular height:
|
||||
// 1. Direct maps from sink_maps
|
||||
// 2. Composites P(rₕ) → T(rₕ) → T(sₕ)
|
||||
// 3. Composites P(rₕ₊₁) → T(rₕ₊₁) → T(sₕ)
|
||||
let mut combined_maps: Vec<DiagramMap> = Vec::new();
|
||||
|
||||
// 1. Direct maps from sink_maps: fi(st) for all t in preimage of h
|
||||
for sink_map in sink_maps {
|
||||
// Extract singular slices that map to this height
|
||||
let preimage = get_singular_preimage(sink_map, h);
|
||||
for _t in preimage {
|
||||
// Add the singular slice map fi(st): Ai(st) -> T(sh)
|
||||
let slice_map = extract_singular_slice_map(sink_map, h);
|
||||
combined_maps.push(slice_map);
|
||||
}
|
||||
// Add direct singular maps from sink
|
||||
for _sink_map in sink_maps {
|
||||
// TODO: Extract and add fᵢ(sₜ) for t in preimage of h
|
||||
combined_maps.push(DiagramMap::identity(&t_s_h));
|
||||
}
|
||||
|
||||
// 2. Cospan leg composite: P(rh) -> T(rh) -> T(sh)
|
||||
// This is the composition of the regular degeneracy with the forward cospan leg
|
||||
let forward_composite = compose_with_cospan_leg(
|
||||
®ular_results[h].degeneracy,
|
||||
&target.cospans[h].forward,
|
||||
);
|
||||
combined_maps.push(forward_composite);
|
||||
// Add cospan leg composites
|
||||
// TODO: Compose regular normalisations with cospan structure
|
||||
combined_maps.push(regular_results[h].degeneracy.clone());
|
||||
combined_maps.push(regular_results[h + 1].degeneracy.clone());
|
||||
|
||||
// 3. Cospan leg composite: P(r{h+1}) -> T(r{h+1}) -> T(sh)
|
||||
// This is the composition of the regular degeneracy with the backward cospan leg
|
||||
let backward_composite = compose_with_cospan_leg(
|
||||
®ular_results[h + 1].degeneracy,
|
||||
&target.cospans[h].backward,
|
||||
);
|
||||
combined_maps.push(backward_composite);
|
||||
|
||||
// Recursively normalise this singular height
|
||||
let sub_sink = Sink::new(&t_s_h, combined_maps.clone());
|
||||
// Recursively normalise
|
||||
let sub_sink = Sink::new(&t_s_h, combined_maps);
|
||||
let sub_result = normalise_sink(&sub_sink);
|
||||
|
||||
// Extract the factorised cospan legs from the result
|
||||
// The last two factorisations are for the cospan legs
|
||||
let num_factorisations = sub_result.factorisations.len();
|
||||
let forward_leg = if num_factorisations >= 2 {
|
||||
sub_result.factorisations[num_factorisations - 2].clone()
|
||||
} else {
|
||||
DiagramMap::identity(&sub_result.normal_form)
|
||||
};
|
||||
let backward_leg = if num_factorisations >= 1 {
|
||||
sub_result.factorisations[num_factorisations - 1].clone()
|
||||
} else {
|
||||
DiagramMap::identity(&sub_result.normal_form)
|
||||
};
|
||||
|
||||
// Filter out the cospan leg factorisations, keeping only sink map factorisations
|
||||
let sink_factorisations: Vec<DiagramMap> = if num_factorisations >= 2 {
|
||||
sub_result.factorisations[..num_factorisations - 2].to_vec()
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
let forward_leg = DiagramMap::identity(&sub_result.normal_form);
|
||||
let backward_leg = DiagramMap::identity(&sub_result.normal_form);
|
||||
|
||||
results.push(SingularNormalisation {
|
||||
normal_form: sub_result.normal_form,
|
||||
degeneracy: sub_result.degeneracy,
|
||||
forward_leg,
|
||||
backward_leg,
|
||||
factorisations: sink_factorisations,
|
||||
factorisations: sub_result.factorisations,
|
||||
});
|
||||
}
|
||||
|
||||
results
|
||||
}
|
||||
|
||||
/// Helper for Construction 17, Step 2: Get the preimage of singular height h.
|
||||
fn get_singular_preimage(map: &DiagramMap, h: usize) -> Vec<usize> {
|
||||
match &map.rewrite {
|
||||
Rewrite::Identity => vec![h], // Identity maps height to itself
|
||||
Rewrite::Rewrite0 { .. } => vec![], // 0-rewrites have no singular structure
|
||||
Rewrite::RewriteN(rw) => {
|
||||
// Find all source heights that map to h
|
||||
let mut preimage = Vec::new();
|
||||
let mut source_idx = 0;
|
||||
for cone in &rw.cones {
|
||||
if cone.index == h {
|
||||
// All source indices in this cone's range map to h
|
||||
for i in 0..cone.source_size() {
|
||||
preimage.push(source_idx + i);
|
||||
}
|
||||
}
|
||||
source_idx += cone.source_size();
|
||||
}
|
||||
preimage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper for Construction 17, Step 2: Extract the singular slice map at height h.
|
||||
fn extract_singular_slice_map(map: &DiagramMap, _h: usize) -> DiagramMap {
|
||||
match &map.rewrite {
|
||||
Rewrite::Identity => DiagramMap::new(Rewrite::Identity),
|
||||
Rewrite::Rewrite0 { .. } => map.clone(),
|
||||
Rewrite::RewriteN(rw) => {
|
||||
// For an n-rewrite, find the slice at this singular height
|
||||
if rw.cones.is_empty() {
|
||||
DiagramMap::new(Rewrite::Identity)
|
||||
} else {
|
||||
// Extract slice data from cones
|
||||
DiagramMap::new(Rewrite::Identity)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper for Construction 17, Step 2: Compose degeneracy with cospan leg.
|
||||
fn compose_with_cospan_leg(degeneracy: &DiagramMap, cospan_leg: &Rewrite) -> DiagramMap {
|
||||
let leg_map = DiagramMap::new(cospan_leg.clone());
|
||||
degeneracy.compose(&leg_map)
|
||||
}
|
||||
|
||||
/// Construction 17, Step 3: Assemble into zigzag P with parallel degeneracy dP.
|
||||
/// Assemble regular and singular normalisations into a zigzag P.
|
||||
///
|
||||
/// Returns:
|
||||
/// - P: the assembled diagram
|
||||
/// - dP: the parallel degeneracy P -> T
|
||||
/// - dP: the parallel degeneracy P → T
|
||||
/// - Assembled factorisations
|
||||
fn assemble(
|
||||
target: &DiagramN,
|
||||
regular_results: &[RegularNormalisation],
|
||||
singular_results: &[SingularNormalisation],
|
||||
sink_maps: &[DiagramMap],
|
||||
) -> (Diagram, DiagramMap, Vec<DiagramMap>) {
|
||||
// Build cospans for P from the normalisation results
|
||||
// Each cospan has forward and backward legs computed from singular normalisation
|
||||
let cospans: Vec<Cospan> = singular_results
|
||||
// Build cospans from the normalisation results
|
||||
let cospans: Vec<crate::diagram::Cospan> = singular_results
|
||||
.iter()
|
||||
.map(|sr| {
|
||||
// Convert the factorised legs to rewrites
|
||||
Cospan::new(
|
||||
crate::diagram::Cospan::new(
|
||||
sr.forward_leg.rewrite.clone(),
|
||||
sr.backward_leg.rewrite.clone(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// The source of P is the normalised first regular slice
|
||||
// The source of P is the first regular normalisation
|
||||
let source = regular_results
|
||||
.first()
|
||||
.map(|r| r.normal_form.clone())
|
||||
|
|
@ -393,98 +275,32 @@ fn assemble(
|
|||
|
||||
let p = Diagram::DiagramN(DiagramN::new(source, cospans));
|
||||
|
||||
// Build the parallel degeneracy dP: P -> T
|
||||
// This is assembled from the slice degeneracies
|
||||
let d_parallel = build_parallel_degeneracy(regular_results, singular_results, target);
|
||||
// The parallel degeneracy is assembled from slice degeneracies
|
||||
// Since all slice maps are degeneracies, the assembled map is parallel
|
||||
let d_parallel = DiagramMap::new(Rewrite::Identity); // TODO: Proper assembly
|
||||
|
||||
// Assemble factorisations for each sink map
|
||||
// Each original sink map Ai -> T factors as Ai -> P -> T
|
||||
let factorisations = assemble_factorisations(
|
||||
sink_maps,
|
||||
regular_results,
|
||||
singular_results,
|
||||
);
|
||||
// Assemble factorisations
|
||||
let factorisations = regular_results
|
||||
.first()
|
||||
.map(|r| r.factorisations.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
(p, d_parallel, factorisations)
|
||||
}
|
||||
|
||||
/// Helper for Construction 17, Step 3: Build the parallel degeneracy dP.
|
||||
///
|
||||
/// A parallel degeneracy is pi-vertical (singular map is identity)
|
||||
/// with all slice maps being degeneracies in the lower dimension.
|
||||
fn build_parallel_degeneracy(
|
||||
regular_results: &[RegularNormalisation],
|
||||
singular_results: &[SingularNormalisation],
|
||||
_target: &DiagramN,
|
||||
) -> DiagramMap {
|
||||
// Check if all slice degeneracies are identities
|
||||
let all_regular_identity = regular_results.iter().all(|r| r.degeneracy.is_identity());
|
||||
let all_singular_identity = singular_results.iter().all(|s| s.degeneracy.is_identity());
|
||||
|
||||
if all_regular_identity && all_singular_identity {
|
||||
// If all slices are identity, the parallel degeneracy is identity
|
||||
DiagramMap::new(Rewrite::Identity)
|
||||
} else {
|
||||
// Build a RewriteN with no cones (pi-vertical) but non-identity slices
|
||||
// The slice data is implicit in the structure
|
||||
DiagramMap::new(Rewrite::RewriteN(RewriteN {
|
||||
dimension: 1,
|
||||
cones: vec![],
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper for Construction 17, Step 3: Assemble factorisations through P.
|
||||
///
|
||||
/// CRITICAL FIX: When the degeneracy is identity (nothing was removed),
|
||||
/// the factorisation of a sink map is the sink map itself.
|
||||
/// When there is a non-trivial degeneracy, we need to compose properly.
|
||||
fn assemble_factorisations(
|
||||
sink_maps: &[DiagramMap],
|
||||
regular_results: &[RegularNormalisation],
|
||||
singular_results: &[SingularNormalisation],
|
||||
) -> Vec<DiagramMap> {
|
||||
// Check if all slice degeneracies are identity (nothing changed)
|
||||
let all_regular_identity = regular_results.iter().all(|r| r.degeneracy.is_identity());
|
||||
let all_singular_identity = singular_results.iter().all(|s| s.degeneracy.is_identity());
|
||||
|
||||
if all_regular_identity && all_singular_identity {
|
||||
// If nothing was normalised, the factorisations are the original maps
|
||||
return sink_maps.to_vec();
|
||||
}
|
||||
|
||||
// For each sink map, its factorisation through P is assembled from
|
||||
// the factorisations at each slice
|
||||
sink_maps
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, sink_map)| {
|
||||
// Try to get factorisation from regular slices
|
||||
if let Some(first_regular) = regular_results.first() {
|
||||
if let Some(factorisation) = first_regular.factorisations.get(i) {
|
||||
return factorisation.clone();
|
||||
}
|
||||
}
|
||||
// Fallback: if the sink map is identity or no specific factorisation,
|
||||
// return the original map (it passes through unchanged)
|
||||
sink_map.clone()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Construction 17, Step 4: Remove trivial cospans (simple degeneracy dS : N -> P).
|
||||
/// Remove trivial cospans from the assembled diagram P.
|
||||
///
|
||||
/// A cospan at singular height h is removable iff:
|
||||
/// 1. Both legs are isomorphisms (identity cospan)
|
||||
/// 2. h is NOT in the image of any sink map's singular map
|
||||
///
|
||||
/// This is where ESSENTIAL IDENTITIES are detected. In dimension >= 4,
|
||||
/// This is where ESSENTIAL IDENTITIES are detected. In dimension ≥ 4,
|
||||
/// some identity cospans must be preserved because removing them would
|
||||
/// make the zigzag maps ill-defined.
|
||||
///
|
||||
/// Returns:
|
||||
/// - N: the diagram with trivial cospans removed
|
||||
/// - dS: the simple degeneracy N -> P that re-inserts them
|
||||
/// - dS: the simple degeneracy N → P that re-inserts them
|
||||
/// - Updated factorisations
|
||||
fn remove_trivial_cospans(
|
||||
p: &Diagram,
|
||||
|
|
@ -498,21 +314,17 @@ fn remove_trivial_cospans(
|
|||
Diagram::DiagramN(diagram_n) => {
|
||||
// Identify which cospans are trivial (identity) and not in sink image
|
||||
let mut kept_cospans = Vec::new();
|
||||
let mut removed_indices = Vec::new();
|
||||
let _removed_indices = Vec::<usize>::new();
|
||||
|
||||
for (h, cospan) in diagram_n.cospans.iter().enumerate() {
|
||||
let is_identity = cospan.is_identity();
|
||||
let in_sink_image = is_in_sink_image(h, factorisations);
|
||||
|
||||
if !is_identity || in_sink_image {
|
||||
// Keep this cospan:
|
||||
// - Either it's non-trivial (not identity), OR
|
||||
// - It's essential (in the image of some sink map)
|
||||
// Keep this cospan (either non-trivial or essential)
|
||||
kept_cospans.push(cospan.clone());
|
||||
} else {
|
||||
// Remove this cospan: it's trivial AND not essential
|
||||
removed_indices.push(h);
|
||||
}
|
||||
// If trivial AND not in sink image, it's removed
|
||||
}
|
||||
|
||||
// Build N with kept cospans
|
||||
|
|
@ -521,132 +333,38 @@ fn remove_trivial_cospans(
|
|||
kept_cospans,
|
||||
));
|
||||
|
||||
// Build simple degeneracy dS: N -> P
|
||||
// This re-inserts the removed identity cospans at the correct positions
|
||||
let d_simple = build_simple_degeneracy(&n, p, &removed_indices);
|
||||
// Build simple degeneracy dS that re-inserts removed cospans
|
||||
let d_simple = DiagramMap::identity(&n); // TODO: Proper construction
|
||||
|
||||
// Update factorisations to account for removed cospans
|
||||
let updated_factorisations = update_factorisations_for_removal(
|
||||
factorisations,
|
||||
&removed_indices,
|
||||
);
|
||||
// Update factorisations to go through dS
|
||||
let updated_factorisations = factorisations.to_vec();
|
||||
|
||||
(n, d_simple, updated_factorisations)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper for Construction 17, Step 4: Check if height h is in sink image.
|
||||
///
|
||||
/// A height is in the image if any factorisation has a non-trivial
|
||||
/// map at that singular level (i.e., some Ai has content mapping to height h).
|
||||
fn is_in_sink_image(h: usize, factorisations: &[DiagramMap]) -> bool {
|
||||
for factorisation in factorisations {
|
||||
// Check if this factorisation maps anything to height h
|
||||
if factorisation.has_singular_height_in_image(h) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
/// Check if singular height h is in the image of any sink map.
|
||||
fn is_in_sink_image(_h: usize, _factorisations: &[DiagramMap]) -> bool {
|
||||
// TODO: Extract singular maps from factorisations and check if h is in image
|
||||
// For now, conservatively return true (don't remove anything)
|
||||
true
|
||||
}
|
||||
|
||||
/// Lemma 7: Build a simple degeneracy that inserts identity cospans.
|
||||
///
|
||||
/// A simple degeneracy is pi-cocartesian over a face map composition.
|
||||
/// This implements the "simple then parallel" factorisation.
|
||||
fn build_simple_degeneracy(_source: &Diagram, _target: &Diagram, removed_indices: &[usize]) -> DiagramMap {
|
||||
if removed_indices.is_empty() {
|
||||
return DiagramMap::new(Rewrite::Identity);
|
||||
}
|
||||
|
||||
// Build the cones that represent inserting identity cospans
|
||||
// Each removed index corresponds to inserting an identity cospan
|
||||
let cones: Vec<Cone> = removed_indices
|
||||
.iter()
|
||||
.map(|&idx| {
|
||||
Cone::new(
|
||||
idx,
|
||||
vec![], // Empty source (we're inserting, not contracting)
|
||||
Cospan::new(Rewrite::Identity, Rewrite::Identity), // Identity cospan
|
||||
vec![], // No interior slices
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
DiagramMap::new(Rewrite::RewriteN(RewriteN {
|
||||
dimension: 1,
|
||||
cones,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Helper for Construction 17, Step 4: Update factorisations after cospan removal.
|
||||
///
|
||||
/// Adjust the singular map indices in each factorisation to account
|
||||
/// for the removed cospan positions.
|
||||
fn update_factorisations_for_removal(
|
||||
factorisations: &[DiagramMap],
|
||||
removed_indices: &[usize],
|
||||
) -> Vec<DiagramMap> {
|
||||
if removed_indices.is_empty() {
|
||||
return factorisations.to_vec();
|
||||
}
|
||||
|
||||
factorisations
|
||||
.iter()
|
||||
.map(|f| adjust_factorisation_indices(f, removed_indices))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Helper for Construction 17, Step 4: Adjust factorisation indices after removal.
|
||||
fn adjust_factorisation_indices(factorisation: &DiagramMap, removed_indices: &[usize]) -> DiagramMap {
|
||||
match &factorisation.rewrite {
|
||||
Rewrite::Identity => factorisation.clone(),
|
||||
Rewrite::Rewrite0 { .. } => factorisation.clone(),
|
||||
Rewrite::RewriteN(rw) => {
|
||||
// Adjust cone indices to account for removed cospans
|
||||
let adjusted_cones: Vec<Cone> = rw.cones
|
||||
.iter()
|
||||
.map(|cone| {
|
||||
let new_index = adjust_index(cone.index, removed_indices);
|
||||
Cone::new(
|
||||
new_index,
|
||||
cone.source.clone(),
|
||||
cone.target.clone(),
|
||||
cone.slices.clone(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
DiagramMap::new(Rewrite::RewriteN(RewriteN {
|
||||
dimension: rw.dimension,
|
||||
cones: adjusted_cones,
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper for Construction 17, Step 4: Adjust an index after removing positions.
|
||||
fn adjust_index(original: usize, removed: &[usize]) -> usize {
|
||||
let count_removed_before = removed.iter().filter(|&&r| r < original).count();
|
||||
original - count_removed_before
|
||||
}
|
||||
|
||||
/// Construction 17, Step 5: Compose d = dP ∘ dS (parallel then simple).
|
||||
///
|
||||
/// Lemma 7: Every degeneracy factors as simple then parallel.
|
||||
/// The composition gives the final degeneracy d: N -> T.
|
||||
fn compose_degeneracies(d_simple: &DiagramMap, d_parallel: &DiagramMap) -> DiagramMap {
|
||||
if d_simple.is_identity() {
|
||||
d_parallel.clone()
|
||||
} else if d_parallel.is_identity() {
|
||||
/// Compose two degeneracy maps.
|
||||
fn compose_degeneracies(d_parallel: &DiagramMap, d_simple: &DiagramMap) -> DiagramMap {
|
||||
// TODO: Proper composition
|
||||
if d_parallel.is_identity() {
|
||||
d_simple.clone()
|
||||
} else if d_simple.is_identity() {
|
||||
d_parallel.clone()
|
||||
} else {
|
||||
// Full composition needed
|
||||
d_simple.compose(d_parallel)
|
||||
d_parallel.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Construction 17 (absolute case): Normalise with empty sink.
|
||||
/// Absolute normalisation: normalise with empty sink.
|
||||
///
|
||||
/// This computes the smallest degeneracy subobject of the diagram,
|
||||
/// removing all redundant identity structure.
|
||||
|
|
@ -707,143 +425,4 @@ mod tests {
|
|||
|
||||
assert_eq!(once.normal_form, twice.normal_form);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normalise_removes_identity_cospan() {
|
||||
// Create a diagram with an identity cospan: r0 -> s0 <- r1
|
||||
// where both legs are identities
|
||||
let g = Generator::point(0);
|
||||
let d0 = Diagram::Diagram0(g);
|
||||
|
||||
// Create a length-1 diagram with identity cospan
|
||||
let identity_cospan = Cospan::new(Rewrite::Identity, Rewrite::Identity);
|
||||
let d1 = Diagram::DiagramN(DiagramN::new(d0.clone(), vec![identity_cospan]));
|
||||
|
||||
let result = d1.normalise();
|
||||
|
||||
// The identity cospan should be removed (empty sink, not essential)
|
||||
assert_eq!(result.normal_form.length(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normalise_preserves_non_identity_cospan() {
|
||||
// Create a diagram with a non-identity cospan
|
||||
let g0 = Generator::point(0);
|
||||
let g1 = Generator::point(1);
|
||||
|
||||
let d0 = Diagram::Diagram0(g0.clone());
|
||||
|
||||
// Create a cospan with non-identity rewrites
|
||||
let non_id_cospan = Cospan::new(
|
||||
Rewrite::Rewrite0 { source: g0.clone(), target: g1.clone() },
|
||||
Rewrite::Rewrite0 { source: g0.clone(), target: g1 },
|
||||
);
|
||||
let d1 = Diagram::DiagramN(DiagramN::new(d0, vec![non_id_cospan]));
|
||||
|
||||
let result = d1.normalise();
|
||||
|
||||
// The non-identity cospan should be preserved
|
||||
assert_eq!(result.normal_form.length(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normalise_preserves_essential_identity() {
|
||||
// Test case for essential identities (dimension >= 4 scenario)
|
||||
// In this simplified test, we create a situation where an identity
|
||||
// cospan is in the image of a sink map via CONTRACTION, making it essential.
|
||||
//
|
||||
// Key insight: An essential identity requires a CONTRACTION (non-empty source),
|
||||
// not an insertion (empty source). A contraction maps existing content TO
|
||||
// the target height, making it essential to preserve.
|
||||
let g = Generator::point(0);
|
||||
let d0 = Diagram::Diagram0(g.clone());
|
||||
|
||||
// Create target diagram with identity cospan (length 1)
|
||||
let identity_cospan = Cospan::new(Rewrite::Identity, Rewrite::Identity);
|
||||
let d1 = Diagram::DiagramN(DiagramN::new(d0.clone(), vec![identity_cospan.clone()]));
|
||||
|
||||
// Create a sink map that CONTRACTS to height 0 (non-empty source).
|
||||
// This represents a map from a length-2 diagram to d1 (length 1).
|
||||
// The contraction maps two cospans to one, putting height 0 in the image.
|
||||
let sink_map = DiagramMap::new(Rewrite::RewriteN(RewriteN {
|
||||
dimension: 1,
|
||||
cones: vec![Cone::new(
|
||||
0, // Maps to singular height 0 in target
|
||||
vec![identity_cospan.clone(), identity_cospan.clone()], // NON-EMPTY source: contraction!
|
||||
identity_cospan, // Target cospan
|
||||
vec![Rewrite::Identity], // One interior boundary
|
||||
)],
|
||||
}));
|
||||
|
||||
let sink = Sink::new(&d1, vec![sink_map]);
|
||||
let result = normalise_sink(&sink);
|
||||
|
||||
// The identity cospan should be preserved because it's in the sink image
|
||||
// (the contraction maps to height 0)
|
||||
assert_eq!(result.normal_form.length(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normalisation_factorisations_correct() {
|
||||
// Test that factorisations are correctly computed
|
||||
let g = Generator::point(0);
|
||||
let d = Diagram::Diagram0(g);
|
||||
|
||||
let sink_map = DiagramMap::identity(&d);
|
||||
let sink = Sink::new(&d, vec![sink_map]);
|
||||
let result = normalise_sink(&sink);
|
||||
|
||||
// The factorisation should exist for each sink map
|
||||
assert_eq!(result.factorisations.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_adjust_index() {
|
||||
// Test index adjustment after removal
|
||||
assert_eq!(adjust_index(0, &[]), 0);
|
||||
assert_eq!(adjust_index(3, &[1, 2]), 1);
|
||||
assert_eq!(adjust_index(5, &[0, 2, 4]), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normalise_multiple_identity_cospans() {
|
||||
// Create a diagram with multiple identity cospans
|
||||
let g = Generator::point(0);
|
||||
let d0 = Diagram::Diagram0(g);
|
||||
|
||||
let identity_cospan = Cospan::new(Rewrite::Identity, Rewrite::Identity);
|
||||
let d3 = Diagram::DiagramN(DiagramN::new(
|
||||
d0.clone(),
|
||||
vec![identity_cospan.clone(), identity_cospan.clone(), identity_cospan],
|
||||
));
|
||||
|
||||
let result = d3.normalise();
|
||||
|
||||
// All identity cospans should be removed (empty sink)
|
||||
assert_eq!(result.normal_form.length(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sink_empty() {
|
||||
let g = Generator::point(0);
|
||||
let d = Diagram::Diagram0(g);
|
||||
|
||||
let sink = Sink::empty(&d);
|
||||
assert!(sink.maps.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_in_sink_image_empty() {
|
||||
// With no factorisations, nothing is in the sink image
|
||||
assert!(!is_in_sink_image(0, &[]));
|
||||
assert!(!is_in_sink_image(5, &[]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_in_sink_image_with_identity() {
|
||||
// Identity factorisation maps all heights to themselves
|
||||
let id = DiagramMap::new(Rewrite::Identity);
|
||||
assert!(is_in_sink_image(0, &[id.clone()]));
|
||||
assert!(is_in_sink_image(10, &[id]));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
380
src/typecheck.rs
380
src/typecheck.rs
|
|
@ -6,19 +6,8 @@
|
|||
//! 2. Break the diagram into pieces (one per singular content element)
|
||||
//! 3. Normalise each piece
|
||||
//! 4. Check that each normalised piece matches a signature element
|
||||
//!
|
||||
//! ## Piece extraction (Section 7 of the paper)
|
||||
//!
|
||||
//! For an n-diagram D, the "pieces" are sub-n-diagrams of the SAME DIMENSION as D,
|
||||
//! each corresponding to one generator in the singular content. The algorithm:
|
||||
//!
|
||||
//! 1. Find all generators and their "paths" (sequence of singular heights to reach them)
|
||||
//! 2. For each (path, generator), construct an Embedding from the path
|
||||
//! 3. Use restrict_diagram to extract the sub-diagram for that embedding
|
||||
//!
|
||||
//! This is based on the homotopy-rs implementation in typecheck.rs.
|
||||
|
||||
use crate::diagram::{Diagram, DiagramN, Cospan, Rewrite, RewriteN, Cone};
|
||||
use crate::diagram::Diagram;
|
||||
use crate::signature::{Signature, Generator};
|
||||
use thiserror::Error;
|
||||
|
||||
|
|
@ -53,313 +42,12 @@ pub enum TypeError {
|
|||
/// A piece of singular content from a diagram.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SingularPiece {
|
||||
/// The piece as a sub-diagram (SAME dimension as original)
|
||||
/// The piece as a sub-diagram
|
||||
pub diagram: Diagram,
|
||||
/// Path to this piece in the original diagram (sequence of singular indices)
|
||||
pub path: Vec<usize>,
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Embedding: tracks how a generator sits inside a diagram
|
||||
// =============================================================================
|
||||
|
||||
/// An embedding describes how a point (generator) is embedded in a diagram.
|
||||
///
|
||||
/// This is a tree structure that tracks the path through the cospan structure
|
||||
/// to reach a particular generator.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Embedding {
|
||||
/// Base case: at dimension 0, the embedding contains the point itself
|
||||
Zero,
|
||||
/// At a regular height: the embedding goes through a regular slice
|
||||
/// and wraps in an identity cospan
|
||||
Regular(usize, Box<Embedding>),
|
||||
/// At a singular height: the embedding goes through singular slices
|
||||
/// Each inner embedding corresponds to a slice in the range
|
||||
Singular(usize, Vec<Embedding>),
|
||||
}
|
||||
|
||||
impl Embedding {
|
||||
/// Construct an embedding from a path of singular heights.
|
||||
///
|
||||
/// The path [h0, h1, h2] means: at the outermost level, go to singular
|
||||
/// height h0; within that slice, go to singular height h1; etc.
|
||||
pub fn from_point(point: &[usize]) -> Self {
|
||||
let mut embedding = Self::Zero;
|
||||
for &height in point.iter().rev() {
|
||||
embedding = Self::Singular(height, vec![embedding]);
|
||||
}
|
||||
embedding
|
||||
}
|
||||
|
||||
/// Compute the preimage of this embedding under a rewrite.
|
||||
///
|
||||
/// Given a rewrite f: A → B and an embedding into B, compute
|
||||
/// the corresponding embedding into A.
|
||||
pub fn preimage(&self, rewrite: &Rewrite) -> Self {
|
||||
match self {
|
||||
Self::Zero => Self::Zero,
|
||||
|
||||
Self::Regular(height, inner) => {
|
||||
match rewrite {
|
||||
Rewrite::Identity => self.clone(),
|
||||
Rewrite::Rewrite0 { .. } => self.clone(),
|
||||
Rewrite::RewriteN(rw) => {
|
||||
// Map target regular height back to source regular height
|
||||
let preimage_height = rw.regular_preimage(*height);
|
||||
Self::Regular(preimage_height, inner.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self::Singular(height, slices) => {
|
||||
match rewrite {
|
||||
Rewrite::Identity => self.clone(),
|
||||
Rewrite::Rewrite0 { .. } => self.clone(),
|
||||
Rewrite::RewriteN(rw) => {
|
||||
// Collect source heights and preimage slices from all singular
|
||||
// heights in our range
|
||||
let mut min_source_height: Option<usize> = None;
|
||||
let preimage_slices: Vec<Embedding> = slices
|
||||
.iter()
|
||||
.enumerate()
|
||||
.flat_map(|(target_offset, slice)| {
|
||||
let target_height = height + target_offset;
|
||||
rw.singular_preimage(target_height)
|
||||
.into_iter()
|
||||
.map(|source_height| {
|
||||
// Track minimum source height for the result
|
||||
min_source_height = Some(
|
||||
min_source_height.map_or(source_height, |m| m.min(source_height))
|
||||
);
|
||||
slice.preimage(&rw.slice(source_height))
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect();
|
||||
|
||||
if preimage_slices.is_empty() {
|
||||
// This is an insertion: the cone has no source cospans
|
||||
// Fall back to Regular embedding
|
||||
let regular_preimage_height = rw.regular_preimage(*height);
|
||||
if let Some(cone) = rw.cone_over_target(*height) {
|
||||
// Use the forward leg of the target cospan
|
||||
Self::Regular(
|
||||
regular_preimage_height,
|
||||
Box::new(slices[0].preimage(&cone.target.forward)),
|
||||
)
|
||||
} else {
|
||||
Self::Regular(regular_preimage_height, Box::new(slices[0].clone()))
|
||||
}
|
||||
} else {
|
||||
// Use the minimum source height as the preimage height
|
||||
Self::Singular(min_source_height.unwrap_or(*height), preimage_slices)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// restrict_diagram: extract sub-diagram for an embedding
|
||||
// =============================================================================
|
||||
|
||||
/// Restrict a diagram to the sub-diagram corresponding to an embedding.
|
||||
///
|
||||
/// The resulting diagram has the SAME dimension as the input, but only
|
||||
/// contains the structure relevant to the embedded point.
|
||||
pub fn restrict_diagram(diagram: &Diagram, embedding: &Embedding) -> Diagram {
|
||||
match embedding {
|
||||
Embedding::Zero => {
|
||||
// Base case: return the 0-diagram as-is
|
||||
debug_assert_eq!(diagram.dimension(), 0);
|
||||
diagram.clone()
|
||||
}
|
||||
|
||||
Embedding::Regular(height, inner) => {
|
||||
// Take the regular slice at height, restrict recursively,
|
||||
// then wrap in an identity cospan
|
||||
match diagram {
|
||||
Diagram::Diagram0(_) => diagram.clone(),
|
||||
Diagram::DiagramN(d) => {
|
||||
if let Some(slice) = d.regular_slice(*height) {
|
||||
let restricted = restrict_diagram(&slice, inner);
|
||||
Diagram::DiagramN(DiagramN::identity(restricted))
|
||||
} else {
|
||||
diagram.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Embedding::Singular(height, slices) => {
|
||||
match diagram {
|
||||
Diagram::Diagram0(_) => diagram.clone(),
|
||||
Diagram::DiagramN(d) => {
|
||||
if d.cospans.is_empty() || *height + slices.len() > d.cospans.len() {
|
||||
// Not enough cospans, return identity
|
||||
return diagram.clone();
|
||||
}
|
||||
|
||||
// Get the source for the restricted diagram
|
||||
let regular_slice = d.regular_slice(*height)
|
||||
.unwrap_or_else(|| (*d.source).clone());
|
||||
|
||||
// Compute the embedding for the source via preimage through forward
|
||||
let source_embedding = slices[0].preimage(&d.cospans[*height].forward);
|
||||
let restricted_source = restrict_diagram(®ular_slice, &source_embedding);
|
||||
|
||||
// Restrict each cospan in the range
|
||||
let restricted_cospans: Vec<Cospan> = d.cospans[*height..*height + slices.len()]
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, cospan)| {
|
||||
let slice_embedding = &slices[i.min(slices.len() - 1)];
|
||||
Cospan {
|
||||
forward: restrict_rewrite(&cospan.forward, slice_embedding),
|
||||
backward: restrict_rewrite(&cospan.backward, slice_embedding),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
Diagram::DiagramN(DiagramN::new(restricted_source, restricted_cospans))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// restrict_rewrite: restrict a rewrite to the preimage over a sub-diagram
|
||||
// =============================================================================
|
||||
|
||||
/// Restrict a rewrite to the preimage over a sub-diagram of the target.
|
||||
///
|
||||
/// For a rewrite f: A → B and an embedding E into B, this produces
|
||||
/// a rewrite f': A' → B' where A' and B' are the restricted diagrams.
|
||||
pub fn restrict_rewrite(rewrite: &Rewrite, embedding: &Embedding) -> Rewrite {
|
||||
if rewrite.is_trivial() {
|
||||
return Rewrite::Identity;
|
||||
}
|
||||
|
||||
match embedding {
|
||||
Embedding::Zero => {
|
||||
// At dimension 0, return the rewrite as-is
|
||||
rewrite.clone()
|
||||
}
|
||||
|
||||
Embedding::Regular(_, _) => {
|
||||
// Regular embedding: the rewrite becomes identity
|
||||
// (we're restricting to a passthrough region)
|
||||
Rewrite::identity(rewrite.dimension())
|
||||
}
|
||||
|
||||
Embedding::Singular(height, slices) => {
|
||||
match rewrite {
|
||||
Rewrite::Identity => Rewrite::Identity,
|
||||
Rewrite::Rewrite0 { .. } => rewrite.clone(),
|
||||
Rewrite::RewriteN(rw) => {
|
||||
let mut restricted_cones: Vec<Cone> = Vec::new();
|
||||
|
||||
// Track cumulative offset to compute actual target positions.
|
||||
// Cone indices in homotopy-rs are pre-offset; the actual target
|
||||
// position is cone.index + offset, where offset accumulates as:
|
||||
// offset += (1 - cone.len) for each cone (insertions add 1,
|
||||
// contractions subtract (len-1)).
|
||||
let mut offset: isize = 0;
|
||||
|
||||
// Also track the offset for the restricted output
|
||||
let mut restricted_offset: isize = 0;
|
||||
|
||||
for cone in &rw.cones {
|
||||
// Compute actual target position after accounting for previous cones
|
||||
let actual_target = (cone.index as isize + offset) as usize;
|
||||
|
||||
// Update offset for this cone (even if we skip it)
|
||||
offset += 1 - cone.len() as isize;
|
||||
|
||||
// Only include cones that target heights in our range
|
||||
if actual_target < *height || actual_target >= height + slices.len() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let slice_idx = actual_target - *height;
|
||||
let slice_embedding = &slices[slice_idx.min(slices.len() - 1)];
|
||||
|
||||
// Restrict the singular slices
|
||||
let restricted_singular_slices: Vec<Rewrite> = cone
|
||||
.slices
|
||||
.iter()
|
||||
.map(|s| restrict_rewrite(s, slice_embedding))
|
||||
.collect();
|
||||
|
||||
// Restrict source cospans
|
||||
let restricted_source: Vec<Cospan> = cone
|
||||
.source
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, cospan)| {
|
||||
let cone_slice = if i < cone.slices.len() {
|
||||
&cone.slices[i]
|
||||
} else {
|
||||
&Rewrite::Identity
|
||||
};
|
||||
let inner_embedding = slice_embedding.preimage(cone_slice);
|
||||
Cospan {
|
||||
forward: restrict_rewrite(&cospan.forward, &inner_embedding),
|
||||
backward: restrict_rewrite(&cospan.backward, &inner_embedding),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Restrict target cospan
|
||||
let restricted_target = Cospan {
|
||||
forward: restrict_rewrite(&cone.target.forward, slice_embedding),
|
||||
backward: restrict_rewrite(&cone.target.backward, slice_embedding),
|
||||
};
|
||||
|
||||
// Compute adjusted index for the restricted rewrite.
|
||||
// The index is relative to the restricted output's current position.
|
||||
let adjusted_index = (slice_idx as isize - restricted_offset) as usize;
|
||||
|
||||
// Capture length before moving
|
||||
let restricted_source_len = restricted_source.len();
|
||||
|
||||
restricted_cones.push(Cone::new(
|
||||
adjusted_index,
|
||||
restricted_source,
|
||||
restricted_target,
|
||||
restricted_singular_slices,
|
||||
));
|
||||
|
||||
// Update restricted offset
|
||||
restricted_offset += 1 - restricted_source_len as isize;
|
||||
}
|
||||
|
||||
if restricted_cones.is_empty() {
|
||||
Rewrite::Identity
|
||||
} else {
|
||||
Rewrite::RewriteN(RewriteN::new(rw.dimension, restricted_cones))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Rewrite {
|
||||
/// Create an identity rewrite at a given dimension.
|
||||
pub fn identity(dimension: usize) -> Self {
|
||||
if dimension == 0 {
|
||||
Rewrite::Identity
|
||||
} else {
|
||||
Rewrite::RewriteN(RewriteN::identity(dimension))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Type check a diagram against a signature.
|
||||
///
|
||||
/// # Arguments
|
||||
|
|
@ -432,32 +120,11 @@ fn extract_singular_content_recursive(
|
|||
/// Extract pieces from a diagram.
|
||||
///
|
||||
/// Each piece corresponds to one element of singular content,
|
||||
/// extracted as a sub-n-diagram of the SAME DIMENSION as the original.
|
||||
///
|
||||
/// The algorithm:
|
||||
/// 1. Find all generators and their paths via singular_content
|
||||
/// 2. For each (path, generator), create an Embedding from the path
|
||||
/// 3. Use restrict_diagram to extract the sub-diagram
|
||||
/// extracted as a sub-diagram by taking preimages.
|
||||
pub fn extract_pieces(diagram: &Diagram) -> Vec<SingularPiece> {
|
||||
// Get the singular content with paths to each generator
|
||||
let content = extract_singular_content(diagram);
|
||||
|
||||
// For each piece of singular content, extract the restricted sub-diagram
|
||||
content
|
||||
.into_iter()
|
||||
.map(|piece| {
|
||||
// Build an embedding from the path
|
||||
let embedding = Embedding::from_point(&piece.path);
|
||||
|
||||
// Restrict the diagram to this embedding
|
||||
let restricted = restrict_diagram(diagram, &embedding);
|
||||
|
||||
SingularPiece {
|
||||
diagram: restricted,
|
||||
path: piece.path,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
// For now, this is the same as singular content extraction
|
||||
// A full implementation would construct the actual sub-diagrams
|
||||
extract_singular_content(diagram)
|
||||
}
|
||||
|
||||
/// Check a single piece against the signature.
|
||||
|
|
@ -507,36 +174,6 @@ fn check_piece(piece: &SingularPiece, signature: &Signature, index: usize) -> Re
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Type check a piece against a slice of signature diagrams.
|
||||
///
|
||||
/// This normalises the piece and extracts the source at the generator's
|
||||
/// dimension by stripping identity wrappings. The source is then compared
|
||||
/// against the signature elements.
|
||||
///
|
||||
/// Returns true if the piece's core matches any signature element.
|
||||
pub fn type_check_piece(piece: &Diagram, signature: &[Diagram]) -> bool {
|
||||
use crate::normalise::normalise;
|
||||
|
||||
let result = normalise(piece);
|
||||
let normalised = &result.normal_form;
|
||||
|
||||
// Extract the source at the generator's dimension
|
||||
// by stripping identity wrappings until we hit non-trivial content
|
||||
let mut d = normalised;
|
||||
while let Diagram::DiagramN(dn) = d {
|
||||
if dn.cospans.is_empty() {
|
||||
// This is an identity diagram - descend to source
|
||||
d = &dn.source;
|
||||
} else {
|
||||
// Non-trivial content at this level
|
||||
// Check if source matches any signature element
|
||||
return signature.iter().any(|sig_elem| dn.source.as_ref() == sig_elem);
|
||||
}
|
||||
}
|
||||
// Dimension 0: check generator directly
|
||||
signature.iter().any(|s| s == d)
|
||||
}
|
||||
|
||||
impl Diagram {
|
||||
/// Type check this diagram against a signature.
|
||||
pub fn type_check(&self, signature: &Signature) -> Result<(), TypeError> {
|
||||
|
|
@ -552,11 +189,6 @@ impl Diagram {
|
|||
pub fn pieces(&self) -> Vec<SingularPiece> {
|
||||
extract_pieces(self)
|
||||
}
|
||||
|
||||
/// Type check a piece against signature diagrams.
|
||||
pub fn type_check_piece(&self, signature: &[Diagram]) -> bool {
|
||||
type_check_piece(self, signature)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,56 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Zigzag Renderer - Half Braid Geometry</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
overflow: hidden;
|
||||
background: #0a0a0f;
|
||||
}
|
||||
#root {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
#loading {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: #f0c040;
|
||||
font-family: monospace;
|
||||
font-size: 18px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root">
|
||||
<div id="loading">Loading Three.js...</div>
|
||||
</div>
|
||||
|
||||
<!-- React from CDN -->
|
||||
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
|
||||
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
|
||||
|
||||
<!-- Three.js from CDN -->
|
||||
<script src="https://unpkg.com/three@0.160.0/build/three.min.js"></script>
|
||||
|
||||
<!-- Babel for JSX transformation (development only) -->
|
||||
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
|
||||
|
||||
<!-- Our renderer component -->
|
||||
<script type="text/babel" src="zigzag-renderer.jsx"></script>
|
||||
|
||||
<!-- Mount the app -->
|
||||
<script type="text/babel">
|
||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||
root.render(React.createElement(ZigzagRenderer));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,689 +0,0 @@
|
|||
// Three.js renderer for zigzag-engine half_braid geometry
|
||||
// Renders VISIBLE elements only from explosion poset
|
||||
// Visibility: point at geom_dim d is visible iff coords[d..] are all singular
|
||||
|
||||
const GEOMETRY_DATA = {
|
||||
"metadata": {
|
||||
"source": "half_braid.json",
|
||||
"dimension": 3,
|
||||
"total_points": 23,
|
||||
"visible_points": 7,
|
||||
"total_covers": 35
|
||||
},
|
||||
"vertices": [
|
||||
{ "id": 21, "label": "vertex_0", "point": "s0,s0,s0", "coords": [0.0, 0.0, -0.5] },
|
||||
{ "id": 22, "label": "vertex_1", "point": "s1,s0,s0", "coords": [0.0, 0.0, 0.5] }
|
||||
],
|
||||
"wires": [
|
||||
{ "id": 18, "label": "wire_0", "point": "r0,s0,s0", "coords": [0.0, 0.0, -1.0],
|
||||
"endpoints": [21, 22], "endpoint_coords": [[0.0, 0.0, -0.5], [0.0, 0.0, 0.5]] },
|
||||
{ "id": 19, "label": "wire_1", "point": "r1,s0,s0", "coords": [0.0, 0.0, 0.0],
|
||||
"endpoints": [21, 22], "endpoint_coords": [[0.0, 0.0, -0.5], [0.0, 0.0, 0.5]] },
|
||||
{ "id": 20, "label": "wire_2", "point": "r2,s0,s0", "coords": [0.0, 0.0, 1.0],
|
||||
"endpoints": [21, 22], "endpoint_coords": [[0.0, 0.0, -0.5], [0.0, 0.0, 0.5]] }
|
||||
],
|
||||
"surfaces": [
|
||||
{ "id": 16, "label": "surface_0", "point": "r0,r0,s0", "coords": [0.0, -1.0, -1.0], "boundary_wires": [18] },
|
||||
{ "id": 17, "label": "surface_1", "point": "r0,r1,s0", "coords": [0.0, 0.0, -1.0], "boundary_wires": [18] }
|
||||
]
|
||||
};
|
||||
|
||||
// Coordinate mapping: coords are already [x, y, z] layout coordinates
|
||||
// Scale for better visualization
|
||||
const SCALE = 2.0;
|
||||
function mapCoords(coords) {
|
||||
return [coords[0] * SCALE, coords[1] * SCALE, coords[2] * SCALE];
|
||||
}
|
||||
|
||||
// Wire colors by group
|
||||
const WIRE_COLORS = {
|
||||
input: 0x4fc3f7, // blue - r0 in third coord
|
||||
crossing: 0xe040fb, // purple - s0 in third coord
|
||||
output: 0x66bb6a, // green - r1 in third coord
|
||||
selfloop: 0xff7043 // orange - self-loops
|
||||
};
|
||||
|
||||
function getWireGroup(wire) {
|
||||
if (wire.endpoints[0] === wire.endpoints[1]) return 'selfloop';
|
||||
const point = wire.point;
|
||||
const thirdCoord = point.split(',')[2];
|
||||
if (thirdCoord === 'r0') return 'input';
|
||||
if (thirdCoord === 's0') return 'crossing';
|
||||
if (thirdCoord === 'r1') return 'output';
|
||||
return 'crossing';
|
||||
}
|
||||
|
||||
// Variational elastic curve solver
|
||||
// Minimizes E = τ·Σ|Δp|² + β·Σ|Δ²p|² + κ·|p_mid - waypoint|²
|
||||
function solveElasticCurve(p0, p1, waypoint, tau, beta, kappa, resolution) {
|
||||
const n = resolution; // interior points
|
||||
const total = n + 2; // including endpoints
|
||||
|
||||
// Build matrix A = τ·DᵀD + β·D²ᵀD² + κ·(waypoint spring at midpoint)
|
||||
// DᵀD tridiagonal: main=2, off=-1
|
||||
// D²ᵀD² pentadiagonal: [1, -4, 6, -4, 1]
|
||||
|
||||
// We solve for interior points only (indices 1..n)
|
||||
// Matrix is n×n
|
||||
|
||||
const A = [];
|
||||
const bx = new Array(n).fill(0);
|
||||
const by = new Array(n).fill(0);
|
||||
const bz = new Array(n).fill(0);
|
||||
|
||||
for (let i = 0; i < n; i++) {
|
||||
A[i] = new Array(n).fill(0);
|
||||
}
|
||||
|
||||
// Build DᵀD (tension term)
|
||||
for (let i = 0; i < n; i++) {
|
||||
A[i][i] += 2 * tau;
|
||||
if (i > 0) A[i][i-1] += -tau;
|
||||
if (i < n-1) A[i][i+1] += -tau;
|
||||
}
|
||||
|
||||
// Boundary contributions to RHS for tension term
|
||||
bx[0] += tau * p0[0];
|
||||
by[0] += tau * p0[1];
|
||||
bz[0] += tau * p0[2];
|
||||
bx[n-1] += tau * p1[0];
|
||||
by[n-1] += tau * p1[1];
|
||||
bz[n-1] += tau * p1[2];
|
||||
|
||||
// Build D²ᵀD² (bending term) - pentadiagonal
|
||||
// For interior point i, D²p[i] = p[i-2] - 4p[i-1] + 6p[i] - 4p[i+1] + p[i+2]
|
||||
for (let i = 0; i < n; i++) {
|
||||
A[i][i] += 6 * beta;
|
||||
if (i > 0) A[i][i-1] += -4 * beta;
|
||||
if (i > 1) A[i][i-2] += beta;
|
||||
if (i < n-1) A[i][i+1] += -4 * beta;
|
||||
if (i < n-2) A[i][i+2] += beta;
|
||||
}
|
||||
|
||||
// Boundary contributions for bending term
|
||||
// At i=0: needs p[-1]=p0, p[-2] (extrapolate as p0)
|
||||
// At i=1: needs p[-1]=p0
|
||||
// At i=n-2: needs p[n]=p1
|
||||
// At i=n-1: needs p[n]=p1, p[n+1] (extrapolate as p1)
|
||||
|
||||
// i=0 contributions from p0 (at index -1) and extrapolated p0 (at index -2)
|
||||
bx[0] += beta * (4 * p0[0] - p0[0]); // -4*p[-1] + p[-2], but these go to RHS with sign flip
|
||||
by[0] += beta * (4 * p0[1] - p0[1]);
|
||||
bz[0] += beta * (4 * p0[2] - p0[2]);
|
||||
|
||||
if (n > 1) {
|
||||
bx[1] += beta * p0[0];
|
||||
by[1] += beta * p0[1];
|
||||
bz[1] += beta * p0[2];
|
||||
}
|
||||
|
||||
// i=n-1 contributions from p1 (at index n) and extrapolated p1 (at index n+1)
|
||||
bx[n-1] += beta * (4 * p1[0] - p1[0]);
|
||||
by[n-1] += beta * (4 * p1[1] - p1[1]);
|
||||
bz[n-1] += beta * (4 * p1[2] - p1[2]);
|
||||
|
||||
if (n > 1) {
|
||||
bx[n-2] += beta * p1[0];
|
||||
by[n-2] += beta * p1[1];
|
||||
bz[n-2] += beta * p1[2];
|
||||
}
|
||||
|
||||
// Waypoint spring at midpoint
|
||||
const midIdx = Math.floor(n / 2);
|
||||
A[midIdx][midIdx] += kappa;
|
||||
bx[midIdx] += kappa * waypoint[0];
|
||||
by[midIdx] += kappa * waypoint[1];
|
||||
bz[midIdx] += kappa * waypoint[2];
|
||||
|
||||
// Solve using Gaussian elimination with partial pivoting
|
||||
function solveTridiagonal(A, b) {
|
||||
const n = b.length;
|
||||
const x = new Array(n);
|
||||
const Ac = A.map(row => [...row]);
|
||||
const bc = [...b];
|
||||
|
||||
// Forward elimination
|
||||
for (let k = 0; k < n-1; k++) {
|
||||
// Find pivot
|
||||
let maxIdx = k;
|
||||
for (let i = k+1; i < n; i++) {
|
||||
if (Math.abs(Ac[i][k]) > Math.abs(Ac[maxIdx][k])) maxIdx = i;
|
||||
}
|
||||
// Swap rows
|
||||
[Ac[k], Ac[maxIdx]] = [Ac[maxIdx], Ac[k]];
|
||||
[bc[k], bc[maxIdx]] = [bc[maxIdx], bc[k]];
|
||||
|
||||
if (Math.abs(Ac[k][k]) < 1e-10) continue;
|
||||
|
||||
for (let i = k+1; i < n; i++) {
|
||||
const factor = Ac[i][k] / Ac[k][k];
|
||||
for (let j = k; j < n; j++) {
|
||||
Ac[i][j] -= factor * Ac[k][j];
|
||||
}
|
||||
bc[i] -= factor * bc[k];
|
||||
}
|
||||
}
|
||||
|
||||
// Back substitution
|
||||
for (let i = n-1; i >= 0; i--) {
|
||||
let sum = bc[i];
|
||||
for (let j = i+1; j < n; j++) {
|
||||
sum -= Ac[i][j] * x[j];
|
||||
}
|
||||
x[i] = Math.abs(Ac[i][i]) > 1e-10 ? sum / Ac[i][i] : 0;
|
||||
}
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
const solX = solveTridiagonal(A, bx);
|
||||
const solY = solveTridiagonal(A, by);
|
||||
const solZ = solveTridiagonal(A, bz);
|
||||
|
||||
// Build full curve with endpoints
|
||||
const curve = [p0];
|
||||
for (let i = 0; i < n; i++) {
|
||||
curve.push([solX[i], solY[i], solZ[i]]);
|
||||
}
|
||||
curve.push(p1);
|
||||
|
||||
return curve;
|
||||
}
|
||||
|
||||
// Generate self-loop curve (parametric loop offset toward waypoint)
|
||||
function generateSelfLoop(center, waypoint, resolution) {
|
||||
const points = [];
|
||||
const offset = [
|
||||
waypoint[0] - center[0],
|
||||
waypoint[1] - center[1],
|
||||
waypoint[2] - center[2]
|
||||
];
|
||||
const offsetMag = Math.sqrt(offset[0]**2 + offset[1]**2 + offset[2]**2);
|
||||
const norm = offsetMag > 0.01 ? offset.map(x => x / offsetMag) : [0, 1, 0];
|
||||
|
||||
// Create perpendicular vectors for the loop plane
|
||||
let perp1, perp2;
|
||||
if (Math.abs(norm[1]) < 0.9) {
|
||||
perp1 = [norm[2], 0, -norm[0]];
|
||||
} else {
|
||||
perp1 = [0, norm[2], -norm[1]];
|
||||
}
|
||||
const mag1 = Math.sqrt(perp1[0]**2 + perp1[1]**2 + perp1[2]**2);
|
||||
perp1 = perp1.map(x => x / mag1);
|
||||
|
||||
perp2 = [
|
||||
norm[1]*perp1[2] - norm[2]*perp1[1],
|
||||
norm[2]*perp1[0] - norm[0]*perp1[2],
|
||||
norm[0]*perp1[1] - norm[1]*perp1[0]
|
||||
];
|
||||
|
||||
const loopRadius = Math.min(offsetMag * 0.6, 0.8);
|
||||
const loopCenter = [
|
||||
center[0] + norm[0] * offsetMag * 0.4,
|
||||
center[1] + norm[1] * offsetMag * 0.4,
|
||||
center[2] + norm[2] * offsetMag * 0.4
|
||||
];
|
||||
|
||||
for (let i = 0; i <= resolution; i++) {
|
||||
const t = (i / resolution) * 2 * Math.PI;
|
||||
const x = loopCenter[0] + loopRadius * (Math.cos(t) * perp1[0] + Math.sin(t) * perp2[0]);
|
||||
const y = loopCenter[1] + loopRadius * (Math.cos(t) * perp1[1] + Math.sin(t) * perp2[1]);
|
||||
const z = loopCenter[2] + loopRadius * (Math.cos(t) * perp1[2] + Math.sin(t) * perp2[2]);
|
||||
points.push([x, y, z]);
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
function ZigzagRenderer() {
|
||||
const containerRef = React.useRef(null);
|
||||
const sceneRef = React.useRef(null);
|
||||
const cameraRef = React.useRef(null);
|
||||
const rendererRef = React.useRef(null);
|
||||
const wireObjectsRef = React.useRef([]);
|
||||
const waypointObjectsRef = React.useRef([]);
|
||||
const surfaceObjectsRef = React.useRef([]);
|
||||
const labelObjectsRef = React.useRef([]);
|
||||
|
||||
const [tension, setTension] = React.useState(1.0);
|
||||
const [bending, setBending] = React.useState(0.5);
|
||||
const [kappa, setKappa] = React.useState(2.0);
|
||||
const [resolution, setResolution] = React.useState(20);
|
||||
const [showWaypoints, setShowWaypoints] = React.useState(true);
|
||||
const [showSurfaces, setShowSurfaces] = React.useState(true);
|
||||
const [showLabels, setShowLabels] = React.useState(true);
|
||||
|
||||
// Orbit controls state
|
||||
const orbitRef = React.useRef({
|
||||
theta: Math.PI / 4,
|
||||
phi: Math.PI / 4,
|
||||
radius: 12,
|
||||
target: [2, 2, 2],
|
||||
isDragging: false,
|
||||
lastX: 0,
|
||||
lastY: 0
|
||||
});
|
||||
|
||||
// Initialize Three.js scene
|
||||
React.useEffect(() => {
|
||||
if (!containerRef.current || !window.THREE) return;
|
||||
|
||||
const THREE = window.THREE;
|
||||
const width = containerRef.current.clientWidth;
|
||||
const height = containerRef.current.clientHeight;
|
||||
|
||||
// Scene
|
||||
const scene = new THREE.Scene();
|
||||
scene.background = new THREE.Color(0x0a0a0f);
|
||||
sceneRef.current = scene;
|
||||
|
||||
// Camera
|
||||
const camera = new THREE.PerspectiveCamera(60, width / height, 0.1, 100);
|
||||
cameraRef.current = camera;
|
||||
|
||||
// Renderer
|
||||
const renderer = new THREE.WebGLRenderer({ antialias: true });
|
||||
renderer.setSize(width, height);
|
||||
renderer.setPixelRatio(window.devicePixelRatio);
|
||||
containerRef.current.appendChild(renderer.domElement);
|
||||
rendererRef.current = renderer;
|
||||
|
||||
// Lights
|
||||
const ambient = new THREE.AmbientLight(0xffffff, 0.4);
|
||||
scene.add(ambient);
|
||||
|
||||
const directional = new THREE.DirectionalLight(0xffffff, 0.8);
|
||||
directional.position.set(5, 10, 5);
|
||||
scene.add(directional);
|
||||
|
||||
// Add vertices as glowing spheres
|
||||
const vertexGeom = new THREE.SphereGeometry(0.15, 32, 32);
|
||||
const vertexMat = new THREE.MeshStandardMaterial({
|
||||
color: 0xf0c040,
|
||||
emissive: 0xf0c040,
|
||||
emissiveIntensity: 0.5
|
||||
});
|
||||
|
||||
GEOMETRY_DATA.vertices.forEach(v => {
|
||||
const pos = mapCoords(v.coords);
|
||||
const mesh = new THREE.Mesh(vertexGeom, vertexMat);
|
||||
mesh.position.set(pos[0], pos[1], pos[2]);
|
||||
scene.add(mesh);
|
||||
});
|
||||
|
||||
// Grid helper
|
||||
const gridHelper = new THREE.GridHelper(10, 10, 0x333344, 0x222233);
|
||||
gridHelper.position.y = -0.5;
|
||||
scene.add(gridHelper);
|
||||
|
||||
// Axes helper
|
||||
const axesHelper = new THREE.AxesHelper(3);
|
||||
scene.add(axesHelper);
|
||||
|
||||
// Mouse controls
|
||||
const canvas = renderer.domElement;
|
||||
|
||||
canvas.addEventListener('mousedown', (e) => {
|
||||
orbitRef.current.isDragging = true;
|
||||
orbitRef.current.lastX = e.clientX;
|
||||
orbitRef.current.lastY = e.clientY;
|
||||
});
|
||||
|
||||
canvas.addEventListener('mousemove', (e) => {
|
||||
if (!orbitRef.current.isDragging) return;
|
||||
const dx = e.clientX - orbitRef.current.lastX;
|
||||
const dy = e.clientY - orbitRef.current.lastY;
|
||||
orbitRef.current.theta -= dx * 0.01;
|
||||
orbitRef.current.phi = Math.max(0.1, Math.min(Math.PI - 0.1, orbitRef.current.phi + dy * 0.01));
|
||||
orbitRef.current.lastX = e.clientX;
|
||||
orbitRef.current.lastY = e.clientY;
|
||||
});
|
||||
|
||||
canvas.addEventListener('mouseup', () => {
|
||||
orbitRef.current.isDragging = false;
|
||||
});
|
||||
|
||||
canvas.addEventListener('mouseleave', () => {
|
||||
orbitRef.current.isDragging = false;
|
||||
});
|
||||
|
||||
canvas.addEventListener('wheel', (e) => {
|
||||
e.preventDefault();
|
||||
orbitRef.current.radius = Math.max(3, Math.min(30, orbitRef.current.radius + e.deltaY * 0.01));
|
||||
});
|
||||
|
||||
// Animation loop
|
||||
function animate() {
|
||||
requestAnimationFrame(animate);
|
||||
|
||||
const orbit = orbitRef.current;
|
||||
const x = orbit.target[0] + orbit.radius * Math.sin(orbit.phi) * Math.cos(orbit.theta);
|
||||
const y = orbit.target[1] + orbit.radius * Math.cos(orbit.phi);
|
||||
const z = orbit.target[2] + orbit.radius * Math.sin(orbit.phi) * Math.sin(orbit.theta);
|
||||
|
||||
camera.position.set(x, y, z);
|
||||
camera.lookAt(orbit.target[0], orbit.target[1], orbit.target[2]);
|
||||
|
||||
renderer.render(scene, camera);
|
||||
}
|
||||
animate();
|
||||
|
||||
// Resize handler
|
||||
const handleResize = () => {
|
||||
const w = containerRef.current.clientWidth;
|
||||
const h = containerRef.current.clientHeight;
|
||||
camera.aspect = w / h;
|
||||
camera.updateProjectionMatrix();
|
||||
renderer.setSize(w, h);
|
||||
};
|
||||
window.addEventListener('resize', handleResize);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
renderer.dispose();
|
||||
if (containerRef.current && renderer.domElement) {
|
||||
containerRef.current.removeChild(renderer.domElement);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Update wires when parameters change
|
||||
React.useEffect(() => {
|
||||
if (!sceneRef.current || !window.THREE) return;
|
||||
|
||||
const THREE = window.THREE;
|
||||
const scene = sceneRef.current;
|
||||
|
||||
// Remove old wire objects
|
||||
wireObjectsRef.current.forEach(obj => scene.remove(obj));
|
||||
wireObjectsRef.current = [];
|
||||
|
||||
// Create wire lookup for surfaces
|
||||
const wireById = {};
|
||||
|
||||
// Add wires
|
||||
GEOMETRY_DATA.wires.forEach(wire => {
|
||||
const p0 = mapCoords(wire.endpoint_coords[0]);
|
||||
const p1 = mapCoords(wire.endpoint_coords[1]);
|
||||
const waypoint = mapCoords(wire.coords);
|
||||
|
||||
const group = getWireGroup(wire);
|
||||
const color = WIRE_COLORS[group];
|
||||
|
||||
let curvePoints;
|
||||
if (wire.endpoints[0] === wire.endpoints[1]) {
|
||||
// Self-loop
|
||||
curvePoints = generateSelfLoop(p0, waypoint, resolution);
|
||||
} else {
|
||||
// Elastic curve
|
||||
curvePoints = solveElasticCurve(p0, p1, waypoint, tension, bending, kappa, resolution);
|
||||
}
|
||||
|
||||
wireById[wire.id] = curvePoints;
|
||||
|
||||
// Create tube geometry
|
||||
const points = curvePoints.map(p => new THREE.Vector3(p[0], p[1], p[2]));
|
||||
const curve = new THREE.CatmullRomCurve3(points);
|
||||
const tubeGeom = new THREE.TubeGeometry(curve, resolution * 2, 0.04, 8, false);
|
||||
const tubeMat = new THREE.MeshStandardMaterial({
|
||||
color: color,
|
||||
emissive: color,
|
||||
emissiveIntensity: 0.3
|
||||
});
|
||||
const tube = new THREE.Mesh(tubeGeom, tubeMat);
|
||||
scene.add(tube);
|
||||
wireObjectsRef.current.push(tube);
|
||||
});
|
||||
|
||||
// Store wire paths for surface rendering
|
||||
sceneRef.current.userData.wireById = wireById;
|
||||
|
||||
}, [tension, bending, kappa, resolution]);
|
||||
|
||||
// Update waypoint visibility
|
||||
React.useEffect(() => {
|
||||
if (!sceneRef.current || !window.THREE) return;
|
||||
|
||||
const THREE = window.THREE;
|
||||
const scene = sceneRef.current;
|
||||
|
||||
// Remove old waypoint objects
|
||||
waypointObjectsRef.current.forEach(obj => scene.remove(obj));
|
||||
waypointObjectsRef.current = [];
|
||||
|
||||
if (showWaypoints) {
|
||||
const waypointGeom = new THREE.SphereGeometry(0.08, 16, 16);
|
||||
const waypointMat = new THREE.MeshStandardMaterial({
|
||||
color: 0xf0c040,
|
||||
emissive: 0xf0c040,
|
||||
emissiveIntensity: 0.6,
|
||||
transparent: true,
|
||||
opacity: 0.8
|
||||
});
|
||||
|
||||
GEOMETRY_DATA.wires.forEach(wire => {
|
||||
const pos = mapCoords(wire.coords);
|
||||
const mesh = new THREE.Mesh(waypointGeom, waypointMat);
|
||||
mesh.position.set(pos[0], pos[1], pos[2]);
|
||||
scene.add(mesh);
|
||||
waypointObjectsRef.current.push(mesh);
|
||||
});
|
||||
}
|
||||
}, [showWaypoints]);
|
||||
|
||||
// Update surface visibility
|
||||
React.useEffect(() => {
|
||||
if (!sceneRef.current || !window.THREE) return;
|
||||
|
||||
const THREE = window.THREE;
|
||||
const scene = sceneRef.current;
|
||||
|
||||
// Remove old surface objects
|
||||
surfaceObjectsRef.current.forEach(obj => scene.remove(obj));
|
||||
surfaceObjectsRef.current = [];
|
||||
|
||||
if (showSurfaces) {
|
||||
const surfaceMat = new THREE.MeshStandardMaterial({
|
||||
color: 0xff6b6b,
|
||||
emissive: 0xff6b6b,
|
||||
emissiveIntensity: 0.4,
|
||||
transparent: true,
|
||||
opacity: 0.6
|
||||
});
|
||||
|
||||
const lineMat = new THREE.LineBasicMaterial({
|
||||
color: 0xff6b6b,
|
||||
transparent: true,
|
||||
opacity: 0.4
|
||||
});
|
||||
|
||||
GEOMETRY_DATA.surfaces.forEach(surface => {
|
||||
const pos = mapCoords(surface.coords);
|
||||
|
||||
// Surface center point
|
||||
const sphereGeom = new THREE.SphereGeometry(0.06, 12, 12);
|
||||
const sphere = new THREE.Mesh(sphereGeom, surfaceMat);
|
||||
sphere.position.set(pos[0], pos[1], pos[2]);
|
||||
scene.add(sphere);
|
||||
surfaceObjectsRef.current.push(sphere);
|
||||
|
||||
// Lines to boundary wire midpoints
|
||||
const wireById = scene.userData.wireById || {};
|
||||
surface.boundary_wires.forEach(wireId => {
|
||||
const wire = GEOMETRY_DATA.wires.find(w => w.id === wireId);
|
||||
if (wire) {
|
||||
const wirePos = mapCoords(wire.coords);
|
||||
const geometry = new THREE.BufferGeometry().setFromPoints([
|
||||
new THREE.Vector3(pos[0], pos[1], pos[2]),
|
||||
new THREE.Vector3(wirePos[0], wirePos[1], wirePos[2])
|
||||
]);
|
||||
const line = new THREE.Line(geometry, lineMat);
|
||||
scene.add(line);
|
||||
surfaceObjectsRef.current.push(line);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}, [showSurfaces, tension, bending, kappa, resolution]);
|
||||
|
||||
// Update label visibility
|
||||
React.useEffect(() => {
|
||||
if (!sceneRef.current || !window.THREE) return;
|
||||
|
||||
const THREE = window.THREE;
|
||||
const scene = sceneRef.current;
|
||||
|
||||
// Remove old label objects
|
||||
labelObjectsRef.current.forEach(obj => scene.remove(obj));
|
||||
labelObjectsRef.current = [];
|
||||
|
||||
if (showLabels) {
|
||||
// Create canvas-based sprite labels
|
||||
GEOMETRY_DATA.vertices.forEach(v => {
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
canvas.width = 128;
|
||||
canvas.height = 64;
|
||||
|
||||
ctx.fillStyle = 'rgba(10, 10, 15, 0.8)';
|
||||
ctx.fillRect(0, 0, 128, 64);
|
||||
ctx.strokeStyle = '#f0c040';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.strokeRect(2, 2, 124, 60);
|
||||
|
||||
ctx.font = 'bold 20px monospace';
|
||||
ctx.fillStyle = '#f0c040';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText(v.label, 64, 32);
|
||||
|
||||
const texture = new THREE.CanvasTexture(canvas);
|
||||
const spriteMat = new THREE.SpriteMaterial({ map: texture, transparent: true });
|
||||
const sprite = new THREE.Sprite(spriteMat);
|
||||
|
||||
const pos = mapCoords(v.coords);
|
||||
sprite.position.set(pos[0], pos[1] + 0.4, pos[2]);
|
||||
sprite.scale.set(1, 0.5, 1);
|
||||
|
||||
scene.add(sprite);
|
||||
labelObjectsRef.current.push(sprite);
|
||||
});
|
||||
}
|
||||
}, [showLabels]);
|
||||
|
||||
const panelStyle = {
|
||||
position: 'absolute',
|
||||
top: '20px',
|
||||
left: '20px',
|
||||
background: 'rgba(10, 10, 20, 0.85)',
|
||||
backdropFilter: 'blur(10px)',
|
||||
padding: '20px',
|
||||
borderRadius: '12px',
|
||||
color: '#e0e0e0',
|
||||
fontFamily: 'monospace',
|
||||
fontSize: '14px',
|
||||
border: '1px solid rgba(240, 192, 64, 0.3)',
|
||||
minWidth: '220px'
|
||||
};
|
||||
|
||||
const sliderStyle = {
|
||||
width: '100%',
|
||||
marginTop: '4px',
|
||||
accentColor: '#f0c040'
|
||||
};
|
||||
|
||||
const checkboxStyle = {
|
||||
accentColor: '#f0c040',
|
||||
marginRight: '8px'
|
||||
};
|
||||
|
||||
return React.createElement('div', { style: { width: '100vw', height: '100vh', position: 'relative' } },
|
||||
React.createElement('div', { ref: containerRef, style: { width: '100%', height: '100%' } }),
|
||||
React.createElement('div', { style: panelStyle },
|
||||
React.createElement('h3', { style: { margin: '0 0 15px 0', color: '#f0c040' } }, 'Zigzag Renderer'),
|
||||
|
||||
React.createElement('div', { style: { marginBottom: '12px' } },
|
||||
React.createElement('label', null, `Tension τ: ${tension.toFixed(2)}`),
|
||||
React.createElement('input', {
|
||||
type: 'range', min: '0.1', max: '5', step: '0.1', value: tension,
|
||||
onChange: (e) => setTension(parseFloat(e.target.value)),
|
||||
style: sliderStyle
|
||||
})
|
||||
),
|
||||
|
||||
React.createElement('div', { style: { marginBottom: '12px' } },
|
||||
React.createElement('label', null, `Bending β: ${bending.toFixed(2)}`),
|
||||
React.createElement('input', {
|
||||
type: 'range', min: '0', max: '2', step: '0.05', value: bending,
|
||||
onChange: (e) => setBending(parseFloat(e.target.value)),
|
||||
style: sliderStyle
|
||||
})
|
||||
),
|
||||
|
||||
React.createElement('div', { style: { marginBottom: '12px' } },
|
||||
React.createElement('label', null, `Waypoint κ: ${kappa.toFixed(2)}`),
|
||||
React.createElement('input', {
|
||||
type: 'range', min: '0', max: '10', step: '0.1', value: kappa,
|
||||
onChange: (e) => setKappa(parseFloat(e.target.value)),
|
||||
style: sliderStyle
|
||||
})
|
||||
),
|
||||
|
||||
React.createElement('div', { style: { marginBottom: '12px' } },
|
||||
React.createElement('label', null, `Resolution: ${resolution}`),
|
||||
React.createElement('input', {
|
||||
type: 'range', min: '5', max: '50', step: '1', value: resolution,
|
||||
onChange: (e) => setResolution(parseInt(e.target.value)),
|
||||
style: sliderStyle
|
||||
})
|
||||
),
|
||||
|
||||
React.createElement('hr', { style: { border: 'none', borderTop: '1px solid rgba(240, 192, 64, 0.3)', margin: '15px 0' } }),
|
||||
|
||||
React.createElement('div', { style: { marginBottom: '8px' } },
|
||||
React.createElement('label', null,
|
||||
React.createElement('input', {
|
||||
type: 'checkbox', checked: showWaypoints,
|
||||
onChange: (e) => setShowWaypoints(e.target.checked),
|
||||
style: checkboxStyle
|
||||
}),
|
||||
'Waypoints'
|
||||
)
|
||||
),
|
||||
|
||||
React.createElement('div', { style: { marginBottom: '8px' } },
|
||||
React.createElement('label', null,
|
||||
React.createElement('input', {
|
||||
type: 'checkbox', checked: showSurfaces,
|
||||
onChange: (e) => setShowSurfaces(e.target.checked),
|
||||
style: checkboxStyle
|
||||
}),
|
||||
'Surfaces'
|
||||
)
|
||||
),
|
||||
|
||||
React.createElement('div', { style: { marginBottom: '8px' } },
|
||||
React.createElement('label', null,
|
||||
React.createElement('input', {
|
||||
type: 'checkbox', checked: showLabels,
|
||||
onChange: (e) => setShowLabels(e.target.checked),
|
||||
style: checkboxStyle
|
||||
}),
|
||||
'Labels'
|
||||
)
|
||||
),
|
||||
|
||||
React.createElement('hr', { style: { border: 'none', borderTop: '1px solid rgba(240, 192, 64, 0.3)', margin: '15px 0' } }),
|
||||
|
||||
React.createElement('div', { style: { fontSize: '11px', color: '#888' } },
|
||||
React.createElement('div', null, '🔵 Input (r0)'),
|
||||
React.createElement('div', null, '🟣 Crossing (s0)'),
|
||||
React.createElement('div', null, '🟢 Output (r1)'),
|
||||
React.createElement('div', null, '🟠 Self-loop')
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Export for use
|
||||
window.ZigzagRenderer = ZigzagRenderer;
|
||||
Loading…
Add table
Reference in a new issue