use serde::{Deserialize, Serialize};
use crate::room_graph::RoomGraph;
use crate::sheaf_state::SheafSections;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Obstruction {
pub magnitude: f64,
pub mismatch_edges: Vec<(String, String, f64)>,
pub h1_dimension: usize,
}
pub fn compute_obstruction(sections: &SheafSections, graph: &RoomGraph) -> Obstruction {
let mut mismatch_edges: Vec<(String, String, f64)> = Vec::new();
let mut total_magnitude: f64 = 0.0;
for (u, v, _weight) in &graph.edges {
let mismatch = compute_edge_mismatch(sections, u, v);
if mismatch > 1e-15 {
mismatch_edges.push((u.clone(), v.clone(), mismatch));
total_magnitude += mismatch * mismatch;
}
}
total_magnitude = total_magnitude.sqrt();
let h1_dimension = mismatch_edges.len();
Obstruction {
magnitude: total_magnitude,
mismatch_edges,
h1_dimension,
}
}
fn compute_edge_mismatch(sections: &SheafSections, u: &str, v: &str) -> f64 {
let sec_u = sections.get(u);
let sec_v = sections.get(v);
match (sec_u, sec_v) {
(Some(su), Some(sv)) => {
let forward = su.restriction_maps.iter()
.find(|(id, _)| id == v)
.map(|(_, restricted)| {
l2_distance(restricted, &sv.data)
})
.unwrap_or(0.0);
let backward = sv.restriction_maps.iter()
.find(|(id, _)| id == u)
.map(|(_, restricted)| {
l2_distance(restricted, &su.data)
})
.unwrap_or(0.0);
let direct = l2_distance(&su.data, &sv.data);
if su.restriction_maps.iter().any(|(id, _)| id == v)
|| sv.restriction_maps.iter().any(|(id, _)| id == u)
{
(forward + backward) / 2.0
} else {
direct
}
}
_ => 0.0,
}
}
fn l2_distance(a: &[f64], b: &[f64]) -> f64 {
a.iter().zip(b.iter())
.map(|(x, y)| (x - y) * (x - y))
.sum::<f64>()
.sqrt()
}
pub fn compute_direct_obstruction(sections: &SheafSections, graph: &RoomGraph) -> Obstruction {
let mut mismatch_edges: Vec<(String, String, f64)> = Vec::new();
let mut total_sq: f64 = 0.0;
for (u, v, _weight) in &graph.edges {
if let (Some(su), Some(sv)) = (sections.get(u), sections.get(v)) {
let d = l2_distance(&su.data, &sv.data);
if d > 1e-15 {
mismatch_edges.push((u.clone(), v.clone(), d));
total_sq += d * d;
}
}
}
Obstruction {
magnitude: total_sq.sqrt(),
h1_dimension: mismatch_edges.len(),
mismatch_edges,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sheaf_state::RoomSection;
#[test]
fn single_room_h1_zero() {
let sections = SheafSections::new(vec![
RoomSection::new("A", vec![1.0, 2.0, 3.0]),
]);
let graph = RoomGraph::new(vec!["A".into()], vec![]);
let obs = compute_direct_obstruction(§ions, &graph);
assert!((obs.magnitude - 0.0).abs() < 1e-10);
assert_eq!(obs.h1_dimension, 0);
}
#[test]
fn two_rooms_same_data_h1_zero() {
let sections = SheafSections::new(vec![
RoomSection::new("A", vec![1.0, 2.0]),
RoomSection::new("B", vec![1.0, 2.0]),
]);
let graph = RoomGraph::new(
vec!["A".into(), "B".into()],
vec![("A".into(), "B".into(), 1.0)],
);
let obs = compute_direct_obstruction(§ions, &graph);
assert!(obs.magnitude < 1e-10);
assert_eq!(obs.h1_dimension, 0);
}
#[test]
fn two_rooms_different_data_h1_positive() {
let sections = SheafSections::new(vec![
RoomSection::new("A", vec![1.0, 2.0]),
RoomSection::new("B", vec![3.0, 4.0]),
]);
let graph = RoomGraph::new(
vec!["A".into(), "B".into()],
vec![("A".into(), "B".into(), 1.0)],
);
let obs = compute_direct_obstruction(§ions, &graph);
assert!(obs.magnitude > 0.0);
assert_eq!(obs.h1_dimension, 1);
assert!((obs.magnitude - 8.0_f64.sqrt()).abs() < 1e-10);
}
#[test]
fn obstruction_with_restriction_maps() {
let sections = SheafSections::new(vec![
RoomSection::new("A", vec![1.0, 2.0])
.with_restriction("B", vec![1.5, 2.5]),
RoomSection::new("B", vec![3.0, 4.0])
.with_restriction("A", vec![2.5, 3.5]),
]);
let graph = RoomGraph::new(
vec!["A".into(), "B".into()],
vec![("A".into(), "B".into(), 1.0)],
);
let obs = compute_obstruction(§ions, &graph);
assert!(obs.magnitude > 0.0);
}
#[test]
fn three_rooms_chain_obstruction() {
let sections = SheafSections::new(vec![
RoomSection::new("A", vec![0.0]),
RoomSection::new("B", vec![1.0]),
RoomSection::new("C", vec![2.0]),
]);
let graph = RoomGraph::new(
vec!["A".into(), "B".into(), "C".into()],
vec![
("A".into(), "B".into(), 1.0),
("B".into(), "C".into(), 1.0),
],
);
let obs = compute_direct_obstruction(§ions, &graph);
assert!(obs.magnitude > 0.0);
assert_eq!(obs.h1_dimension, 2);
}
#[test]
fn fully_consistent_sections_zero_obstruction() {
let sections = SheafSections::new(vec![
RoomSection::new("A", vec![5.0])
.with_restriction("B", vec![5.0]),
RoomSection::new("B", vec![5.0])
.with_restriction("A", vec![5.0]),
]);
let graph = RoomGraph::new(
vec!["A".into(), "B".into()],
vec![("A".into(), "B".into(), 1.0)],
);
let obs = compute_obstruction(§ions, &graph);
assert!(obs.magnitude < 1e-10);
}
}