tawasuyu

tawasuyu/hifas

Motor en Rust para decidir, con demostración y no a ojo, cuándo un conjunto de réplicas local-first puede operar sin coordinación y cuándo no — y, cuando la respuesta es "sí, dentro de límites", proveer el mecanismo que lo garantiza.

Rust 24Markdown 14TOML 1358 Definitions

⎇ 018b9715e34418b865a7dfbebb213dc72fdd3985 src/verdict.rs Rust · 126 lines · 4.4 KB History Who touched what raw

T3: backend GF(2) exacto + migración a aritmética sin epsilon 018b9715e · Sergio ·

1//! `verdict` — traducción del resultado topológico a lenguaje de sistemas (§6.5).
2//!
3//! `H¹ = 0 → corre libre` vs `H¹ ≠ 0 → coordina aquí`, con localización.
4
5use std::collections::BTreeSet;
6use std::fmt;
7
8use crate::cohomology::Cohomology;
9use crate::nerve::ReplicaId;
10use crate::sheaf::Sheaf;
11
12/// Umbral para considerar no nula una componente del vector de obstrucción.
13const SUPPORT_EPS: f64 = 1e-9;
14
15/// Un nudo: una obstrucción localizada entre réplicas/aristas concretas.
16#[derive(Debug, Clone, PartialEq)]
17pub struct Knot {
18    /// Réplicas implicadas en el nudo.
19    pub replicas: Vec<ReplicaId>,
20    /// Aristas del ciclo obstruido (índices en el nervio).
21    pub edges: Vec<usize>,
22}
23
24/// El veredicto final en lenguaje de sistemas.
25#[derive(Debug, Clone, PartialEq)]
26pub enum Verdict {
27    /// `H¹ = 0`: fusión libre de coordinación.
28    RunsFree,
29    /// `H¹ ≠ 0`: hace falta coordinar, con los nudos localizados.
30    NeedsCoordination {
31        /// Los nudos detectados (uno por generador del cokernel).
32        knots: Vec<Knot>,
33    },
34}
35
36impl Verdict {
37    /// Diagnostica la configuración a partir del haz y su cohomología.
38    ///
39    /// Si `H¹ = 0` → `RunsFree`. Si no, cada vector de la base del cokernel es un
40    /// nudo independiente: las aristas con soporte no nulo lo forman, y sus
41    /// extremos son las réplicas implicadas (§7 paso 5).
42    pub fn diagnose(sheaf: &Sheaf, coho: &Cohomology) -> Verdict {
43        if coho.dim_h1 == 0 {
44            return Verdict::RunsFree;
45        }
46
47        // Offsets de cada arista dentro de C¹ (donde vive el vector de obstrucción).
48        let mut edge_offsets = Vec::with_capacity(sheaf.edge_stalks.len());
49        let mut acc = 0;
50        for &d in &sheaf.edge_stalks {
51            edge_offsets.push(acc);
52            acc += d;
53        }
54
55        let mut knots = Vec::new();
56        for basis_vec in &coho.obstruction_basis {
57            let mut edges = Vec::new();
58            let mut replicas = BTreeSet::new();
59            for (e, (&off, &d)) in edge_offsets.iter().zip(&sheaf.edge_stalks).enumerate() {
60                let touches = basis_vec[off..off + d].iter().any(|x| x.abs() > SUPPORT_EPS);
61                if touches {
62                    edges.push(e);
63                    let (u, v) = sheaf.edge_endpoints[e];
64                    replicas.insert(u);
65                    replicas.insert(v);
66                }
67            }
68            knots.push(Knot {
69                replicas: replicas.into_iter().map(ReplicaId).collect(),
70                edges,
71            });
72        }
73
74        Verdict::NeedsCoordination { knots }
75    }
76}
77
78impl fmt::Display for Verdict {
79    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80        match self {
81            Verdict::RunsFree => write!(f, "H¹ = 0 → corre libre (sin jefe)"),
82            Verdict::NeedsCoordination { knots } => {
83                write!(f, "H¹ ≠ 0 → coordina aquí")?;
84                for knot in knots {
85                    let reps: Vec<usize> = knot.replicas.iter().map(|r| r.0).collect();
86                    write!(f, "\n    · nudo entre réplicas {reps:?} (aristas {:?})", knot.edges)?;
87                }
88                Ok(())
89            }
90        }
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97    use crate::cohomology::compute_with;
98    use crate::gf2::Gf2Backend;
99
100    #[test]
101    fn arbol_corre_libre() {
102        let sheaf = Sheaf::constant(3, &[(0, 1), (1, 2)], 1);
103        let coho = compute_with(&sheaf, &Gf2Backend).unwrap();
104        assert_eq!(Verdict::diagnose(&sheaf, &coho), Verdict::RunsFree);
105    }
106
107    #[test]
108    fn dos_triangulos_reportan_dos_nudos() {
109        // Dos triángulos que comparten la arista (1,2): b₁ = 2 → dos ciclos
110        // obstruidos, luego dos nudos (uno por generador del cokernel).
111        let edges = [(0, 1), (0, 2), (1, 2), (1, 3), (2, 3)];
112        let sheaf = Sheaf::constant(4, &edges, 1);
113        let coho = compute_with(&sheaf, &Gf2Backend).unwrap();
114        match Verdict::diagnose(&sheaf, &coho) {
115            Verdict::NeedsCoordination { knots } => {
116                assert_eq!(knots.len(), 2, "todos los ciclos obstruidos, no solo uno");
117                // Cada nudo es un ciclo real: al menos 3 aristas y 3 réplicas.
118                for k in &knots {
119                    assert!(k.edges.len() >= 3);
120                    assert!(k.replicas.len() >= 3);
121                }
122            }
123            Verdict::RunsFree => panic!("dos ciclos deberían obstruir"),
124        }
125    }
126}