T5: retículos y restricciones de Galois (pista de Tarski)

Addendum §D/T5, aditivo — no toca el motor lineal:
- lattice::LatticeCell (join/meet/bottom/top) y GaloisRestriction
  (lower/upper adjuntos, ley lower(a)≤b ⟺ a≤upper(b)).
- Encodings §D.1: Nat (casilla GCounter, cadena ℕ con ⊤=∞) y ResetVal
  (ResettableRegister; post-reset incomparable → join de generaciones
  distintas colapsa a ⊤, meet a ⊥: la obstrucción semántica de §C).
- IdentityGalois y ScaleGalois (adjunción k·a≤b ⟺ a≤⌊b/k⌋).
- proptest: leyes de retículo (conmut/asoc/idemp/absorción/neutros) para
  Nat y ResetVal, colapso de incomparables, ley adjunta de Galois y
  monotonía de lower.

ADDENDUM.md incorporado (antes addendumsdd.txt). 29 tests, clippy limpio.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sergio
2026-07-01 19:04:47 +00:00
co-authored by Claude Opus 4.8
parent c4920fdc5e
commit fc986d7cd7
3 changed files with 483 additions and 0 deletions
+220
View File
@@ -0,0 +1,220 @@
# SDD — Addendum: Pista de retículos (Tarski)
**Extiende:** `DESIGN.md` (SDD v1) — este documento añade el hito `M-research` y los tickets `T5``T7`.
**Estado:** Diseño — habilitado por el hallazgo del cierre de M3.
**Precondición:** MVP cerrado (M0M3, objetivos G1G6). Batería del oráculo verde. `DISCREPANCIES.md` existe.
**Última edición:** 2026-07-01
---
## A. Por qué existe esta pista (el hallazgo de M3, en una línea)
El motor lineal del MVP, en el régimen monótono, **no mide consistencia: mide la topología del grafo.**
Su codificación colapsa al haz constante, cuya cohomología es `dim H¹ = β₁` (§7.2). Consecuencia probada:
`H¹ = 0 ⟺ grafo acíclico`. Sobre grafos cíclicos con datos monótonos pide coordinación de más
(triángulo-monótono, cuadrado-monótono en `DISCREPANCIES.md`).
Causa raíz, con nombre: **la resta del coboundary.** `(δx)_e = R·x_u R·x_v` exige un menos.
Un join-semilattice no tiene inversos; al linealizarlo, el δ mide *desacuerdo-XOR* en vez de
*compatibilidad-por-join*, e inventa obstrucciones-fantasma alrededor de los ciclos. Por eso el fallo
es conservador y nunca inverso (consistente con §2.2 / R1).
**El objetivo de esta pista es eliminar la resta.** No parchear el motor lineal, sino sustituir su
núcleo por una teoría que reconcilie datos de retículo sin restar.
---
## B. El giro matemático
De vectores a retículos; de `rank` a punto fijo.
| MVP lineal (§7) | Pista de retículos |
|---|---|
| Stalks = espacios vectoriales | Stalks = retículos (u order-lattices) |
| Restricciones = matrices lineales | Restricciones = mapas join-preserving (conexiones de Galois) |
| Coboundary `δ` con resta | Laplaciano de Tarski `L` (sin resta) |
| Veredicto = `dim H¹` (¿cokernel = 0?) | Veredicto = ¿la difusión llega a un punto fijo consistente? |
| Se calcula con eliminación gaussiana | Se calcula iterando `L` a punto fijo (Kleene/Tarski) |
**Andamiaje (real, verificado):** Ghrist & Riess, *Cellular Sheaves of Lattices and the Tarski
Laplacian*, Homology, Homotopy and Applications 24(1):325345 (2022); arXiv:2007.04099.
Inicia una teoría de Hodge discreta para haces celulares valuados en retículos y conexiones de Galois.
La pieza central, el **Laplaciano de Tarski** `L`, es un endomorfismo del complejo de cocadenas cuyos
**puntos fijos** dan una cohomología que **coincide con las secciones globales en grado cero**. La
convergencia la garantiza el teorema de punto fijo de Tarski; para stalks finitas o de rango finito,
converge en tiempo finito (cotas por la altura del retículo).
Forma del operador (implementar según Def. 2 / Lemma 7 del paper, no reinventar la fórmula):
`L` se descompone en dos partes que se combinan tomando **meet** sobre las aristas incidentes:
- **expansión**: `F_{v⊴e}^* ∘ F_{v⊴e}` aplicado al estado local (≥ id: nunca pierde información propia);
- **mezcla**: trae el estado del vecino `w` a la arista vía `F_{w⊴e}` y lo devuelve a `v` vía la
adjunta de Galois `F_{v⊴e}^*`.
La difusión propaga información por los mapas de restricción; su punto fijo es la mejor reconciliación
globalmente coherente.
---
## C. Qué significa "obstrucción" ahora (y la honestidad que toca)
**Perdemos el `H¹` limpio.** En el mundo de retículos no hay cokernel: sin resta, no hay álgebra lineal.
La teoría de cohomología superior para haces de retículos es terreno abierto — la propia línea de Ghrist
Riess prioriza las *aplicaciones del Laplaciano* (grado 0, secciones globales) por encima de un `H¹`
cerrado. No prometemos un número de obstrucción; construimos un **diagnóstico dinámico.**
El veredicto pasa de algebraico a dinámico:
1. Siembra cada vértice con su estado local.
2. Itera la difusión de Tarski hasta el punto fijo (harmónico).
3. **Corre libre** si el punto fijo es una sección global genuina que *preserva* la información local
de cada réplica (cada estado local ≤ / compatible con el valor harmónico). Para CRDTs monótonos
alrededor de un ciclo, el punto fijo es exactamente el **join** de los estados → reconcilia sin
colapso → sin obstrucción.
4. **Coordina** si la reconciliación no puede sostener los estados locales sin colapsar (p.ej. la
difusión fuerza el tope ``, señal de que no existe refinamiento común no trivial). Un `reset`
no monótono produce estados incompatibles → colapso → obstrucción real.
**Localización (abierto, R-L1).** En el MVP la base del cokernel señalaba las aristas del nudo. Aquí la
localización es menos crisp: se toma como las aristas cuyo desacuerdo de restricción *sobrevive* al punto
fijo. Es aproximación, no teorema. Empezar por ahí; refinar es investigación.
---
## D. Modelo de tipos (bocetos)
Conviven con los tipos del MVP; no los reemplazan (el motor lineal sigue como oráculo topológico barato).
```rust
/// Un estado local con estructura de retículo. Reemplaza a `Cell` en la pista de retículos.
pub trait LatticeCell: Clone + PartialOrd {
fn join(&self, other: &Self) -> Self; // (asoc., conmut., idempotente)
fn meet(&self, other: &Self) -> Self; // ∧
fn bottom() -> Self; // ⊥
fn top() -> Self; //
}
/// Un mapa de restricción como conexión de Galois: par (lower, upper) adjunto.
/// lower = F_{v⊴e} (join-preserving), upper = F_{v⊴e}^* (meet-preserving).
pub trait GaloisRestriction<A: LatticeCell, B: LatticeCell> {
fn lower(&self, a: &A) -> B; // preserva joins
fn upper(&self, b: &B) -> A; // adjunta; preserva meets
// debe cumplir: lower(a) ≤ b ⟺ a ≤ upper(b)
}
pub struct LatticeSheaf { /* stalks de retículo por vértice/arista + restricciones de Galois */ }
/// El Laplaciano de Tarski como operador monótono sobre el retículo producto C⁰.
pub struct TarskiLaplacian<'a> { sheaf: &'a LatticeSheaf }
impl<'a> TarskiLaplacian<'a> {
/// Una aplicación de L (expansión ∧ mezcla), según Def. 2 / Lemma 7 del paper.
pub fn step(&self, x: &Cochain) -> Cochain { /* ... */ }
/// Itera a punto fijo (Kleene desde ⊥ hacia arriba, o desde la siembra local).
/// Converge en tiempo finito para retículos de altura finita (Lemma 8 + Tarski).
pub fn harmonize(&self, seed: &Cochain) -> Cochain { /* ... */ }
}
```
### D.1 Encodings concretos de los CRDTs del MVP como retículos
- `GCounter` → producto de cadenas `` bajo `max` (join) / `min` (meet). Monótono por construcción.
- `ResettableRegister` → retículo donde el `reset` introduce elementos **incomparables** (no hay `≤`
entre pre-reset y post-reset), de modo que su join no colapsa a un valor consistente. Esto es lo que
produce el colapso a `` en §C.4 y hace que su obstrucción sea real, no topológica.
---
## E. Cambios de módulos
Aditivos, no destructivos:
```
Nuevo src/lattice.rs — LatticeCell, GaloisRestriction, encodings de §D.1
Nuevo src/tarski.rs — LatticeSheaf, TarskiLaplacian, harmonize (punto fijo)
Editar src/verdict.rs — añadir Verdict::via_tarski(...) junto al via_cohomology(...) lineal
Editar src/oracle.rs — reusar el mismo oracle_verdict; el objetivo es que el haz lo iguale
Editar DISCREPANCIES.md — marcar cada discrepancia como RESUELTA / PERSISTE tras la difusión
```
El módulo `cohomology` del MVP **no se toca**: queda como detector topológico barato y como control
histórico. El punto científico es exhibir la diferencia entre ambos veredictos sobre los mismos casos.
---
## F. Criterio de éxito de la pista
Un solo test maestro manda: **los casos de `DISCREPANCIES.md` deben cambiar de color.**
- `triangulo-monotono`, `cuadrado-monotono`: MVP lineal decía "coordina" (falso positivo topológico).
Con Tarski deben decir **"corre libre"**, igualando al oráculo. La difusión reconcilia al join.
- Todo caso no monótono (`reset` en ciclo o en árbol): debe **seguir** diciendo "coordina", ahora por la
razón correcta (colapso semántico), no por topología.
- Invariante nuevo para `proptest`: **monótono sobre grafo arbitrario (cíclico incluido) ⟹ corre libre.**
Este es el invariante que el MVP lineal *no podía* satisfacer; que Tarski lo satisfaga ES la validación
de la Hipótesis H (§2.2) en su forma correcta.
Si esos tres se cumplen, la brecha de modelado de §2.3 queda cerrada y la Hipótesis H pasa de "se
sostiene solo en el caso acíclico" a "se sostiene en general bajo el encoding de retículos".
---
## G. Hito M-research y tickets
### M-research — Haces de retículos con Laplaciano de Tarski
Cierra la brecha §2.3. Prerrequisito de M4: sin esto, cablear a Tawasuyu/Hammer sobre topologías
cíclicas (gossip/mesh) daría falsos positivos de coordinación crónicos.
**T5 — Retículos y restricciones de Galois.**
"Crea `src/lattice.rs` según §D: el trait `LatticeCell` (join/meet/bottom/top) y `GaloisRestriction`
(lower/upper adjuntos con la ley `lower(a) ≤ b ⟺ a ≤ upper(b)`). Implementa los encodings de §D.1:
`GCounter` como producto de cadenas , `ResettableRegister` con post-reset incomparable. Tests: leyes de
retículo (asociatividad, conmutatividad, idempotencia, absorción), monotonía de `lower`, y la ley
adjunta de Galois verificada por `proptest`. No toques el motor lineal."
**T6 — Laplaciano de Tarski y difusión a punto fijo.**
"Crea `src/tarski.rs`: `LatticeSheaf` y `TarskiLaplacian` con `step` (expansión ∧ mezcla, según Def. 2 /
Lemma 7 de GhristRiess 2022) y `harmonize` (itera a punto fijo por Kleene; asume altura finita, corta al
detectar punto fijo). Prueba de cordura CLAVE — el giro respecto al MVP: para el **haz constante de
retículos sobre un ciclo** (triángulo), la difusión debe converger al join y reportar **sin obstrucción**
— exactamente lo contrario del `H¹=β₁` del haz constante lineal (§7.2). Si esa prueba no invierte el
resultado del MVP, el Laplaciano está mal ensamblado. Test de convergencia en tiempo finito acotado por
la altura del retículo."
**T7 — Veredicto por difusión y cierre de discrepancias.**
"Añade `Verdict::via_tarski` en `verdict.rs` (§C: corre libre si el punto fijo preserva lo local; coordina
si colapsa a ). Reejecuta la batería del oráculo por la ruta Tarski. Test maestro (§F): `triangulo-
monotono` y `cuadrado-monotono` pasan a 'corre libre' e igualan al oráculo; los casos no monótonos siguen
en 'coordina'. Añade el invariante `proptest` 'monótono sobre grafo arbitrario ⟹ corre libre'. Actualiza
`DISCREPANCIES.md`: marca cada entrada RESUELTA o PERSISTE, y documenta cualquier caso nuevo donde Tarski y
oráculo discrepen — ese residuo, si existe, es el siguiente resultado científico."
---
## H. Riesgos y preguntas abiertas de la pista
- **R-L1 (localización).** Señalar *qué* aristas forman el nudo es limpio en el MVP (base del cokernel) y
difuso aquí (aristas cuyo desacuerdo sobrevive al punto fijo). Aproximado; refinar es investigación.
- **R-L2 (cohomología superior).** No hay `H¹` cerrado para haces de retículos; solo grado 0 vía puntos
fijos. Si más adelante necesitas obstrucciones de orden superior, es territorio no resuelto en la
literatura — no lo prometas.
- **R-L3 (fidelidad del encoding).** Que `ResettableRegister` colapse correctamente depende de modelar
el post-reset como *incomparable*, no como un valor mayor. Un encoding descuidado volvería a esconder
o inventar conflictos. Cada CRDT necesita su encoding de retículo justificado, igual que en el MVP
necesitaba su encoding lineal (Q1).
- **Q-L1.** ¿Basta el retículo, o algún CRDT real (p.ej. con métricas/pesos) pide ya un quantale? Si
aparece, el horizonte es el Laplaciano de Lawvere (categorías enriquecidas en quantales) y la difusión
de haces no-lineal de Riess et al. — fuera de alcance; anotar y seguir.
---
## I. Referencias añadidas
- R. Ghrist, H. Riess — *Cellular Sheaves of Lattices and the Tarski Laplacian*, Homology, Homotopy and
Applications 24(1):325345 (2022). arXiv:2007.04099. (Andamiaje central de esta pista.)
- H. Riess — tesis doctoral sobre haces valuados en retículos y optimización en redes multi-agente
(Tarski sheaves). (Contexto y aplicaciones a consenso.)
- Y. Zhao, T. Hanks, H. Riess et al. — *Asynchronous nonlinear sheaf diffusion for multi-agent
coordination* (2026); y trabajo relacionado sobre difusión de haces no-lineal / co-diseño enriquecido
en quantales. (Horizonte Q-L1; solo si el retículo se queda corto.)
- (Del SDD v1) Shapiro et al. 2011 (CRDTs); HellersteinAlvaro (CALM); HansenGhrist 2019 (haces
espectrales, base lineal del MVP).
+262
View File
@@ -0,0 +1,262 @@
//! `lattice` — estados de retículo y restricciones de Galois (Addendum §D, T5).
//!
//! La pista de retículos (GhristRiess 2022): reemplaza los espacios vectoriales
//! del MVP por **retículos** y las matrices por **conexiones de Galois**. No toca
//! el motor lineal (`cohomology`), que sigue como oráculo topológico barato.
//!
//! Aquí viven las piezas de grado 0: los tipos de retículo (`Nat`, `ResetVal`) y
//! la interfaz de restricción adjunta (`GaloisRestriction`). El Laplaciano de
//! Tarski que las difunde a punto fijo llega en T6 (`tarski`).
use std::cmp::Ordering;
/// Un estado local con estructura de retículo acotado (Addendum §D).
///
/// `join` () y `meet` (∧) deben ser asociativos, conmutativos, idempotentes y
/// cumplir absorción; `bottom` (⊥) y `top` () son sus neutros/absorbentes.
pub trait LatticeCell: Clone + PartialOrd {
/// Supremo (least upper bound).
fn join(&self, other: &Self) -> Self;
/// Ínfimo ∧ (greatest lower bound).
fn meet(&self, other: &Self) -> Self;
/// Elemento mínimo ⊥.
fn bottom() -> Self;
/// Elemento máximo .
fn top() -> Self;
}
/// Una casilla de `GCounter` como cadena con = ∞ (Addendum §D.1).
///
/// Join = máximo, meet = mínimo. Monótono por construcción: nunca resta. El
/// `GCounter` completo es el **producto** de estas cadenas (se arma en la stalk
/// del haz de retículos en T6).
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Nat(pub u64);
impl Nat {
/// Representación de ∞ (el de la cadena).
pub const INF: Nat = Nat(u64::MAX);
}
impl LatticeCell for Nat {
fn join(&self, other: &Self) -> Self {
Nat(self.0.max(other.0))
}
fn meet(&self, other: &Self) -> Self {
Nat(self.0.min(other.0))
}
fn bottom() -> Self {
Nat(0)
}
fn top() -> Self {
Nat::INF
}
}
/// Un `ResettableRegister` como retículo donde el post-reset es **incomparable**
/// al pre-reset (Addendum §D.1). Dos generaciones distintas no tienen refinamiento
/// común no trivial: su join colapsa a y su meet a ⊥. Ese colapso a es la
/// señal de obstrucción **real** (semántica, no topológica) de la §C.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ResetVal {
/// ⊥.
Bottom,
/// Un valor concreto en una generación. Comparable solo dentro de su generación.
Gen { generation: u64, value: i64 },
/// (colapso: no hay reconciliación no trivial).
Top,
}
impl PartialOrd for ResetVal {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
use ResetVal::*;
match (self, other) {
(Bottom, Bottom) | (Top, Top) => Some(Ordering::Equal),
(Bottom, _) | (_, Top) => Some(Ordering::Less),
(_, Bottom) | (Top, _) => Some(Ordering::Greater),
(
Gen { generation: g1, value: v1 },
Gen { generation: g2, value: v2 },
) => {
if g1 == g2 {
Some(v1.cmp(v2))
} else {
None // generaciones distintas: incomparables
}
}
}
}
}
impl LatticeCell for ResetVal {
fn join(&self, other: &Self) -> Self {
use ResetVal::*;
match (self, other) {
(Bottom, x) | (x, Bottom) => x.clone(),
(Top, _) | (_, Top) => Top,
(
Gen { generation: g1, value: v1 },
Gen { generation: g2, value: v2 },
) => {
if g1 == g2 {
Gen { generation: *g1, value: *v1.max(v2) }
} else {
Top // incomparables → colapso
}
}
}
}
fn meet(&self, other: &Self) -> Self {
use ResetVal::*;
match (self, other) {
(Top, x) | (x, Top) => x.clone(),
(Bottom, _) | (_, Bottom) => Bottom,
(
Gen { generation: g1, value: v1 },
Gen { generation: g2, value: v2 },
) => {
if g1 == g2 {
Gen { generation: *g1, value: *v1.min(v2) }
} else {
Bottom
}
}
}
}
fn bottom() -> Self {
ResetVal::Bottom
}
fn top() -> Self {
ResetVal::Top
}
}
/// Un mapa de restricción como **conexión de Galois** (Addendum §D): un par
/// adjunto `(lower, upper)` que reemplaza a la matriz lineal del MVP.
///
/// Ley adjunta (monótona): `lower(a) ≤ b ⟺ a ≤ upper(b)`. De ella se siguen
/// que `lower` preserva joins y `upper` preserva meets, sin necesidad de restar.
pub trait GaloisRestriction<A: LatticeCell, B: LatticeCell> {
/// `F_{v⊴e}`: sube el estado local a la arista. Preserva joins.
fn lower(&self, a: &A) -> B;
/// `F_{v⊴e}^*`: la adjunta; baja de la arista al vértice. Preserva meets.
fn upper(&self, b: &B) -> A;
}
/// La conexión de Galois identidad: comparten el mismo dato tal cual.
#[derive(Debug, Clone, Copy, Default)]
pub struct IdentityGalois;
impl<L: LatticeCell> GaloisRestriction<L, L> for IdentityGalois {
fn lower(&self, a: &L) -> L {
a.clone()
}
fn upper(&self, b: &L) -> L {
b.clone()
}
}
/// Conexión de Galois no trivial sobre : `lower(a) = k·a`, `upper(b) = ⌊b/k⌋`.
/// Es la adjunción clásica multiplicación/división: `k·a ≤ b ⟺ a ≤ ⌊b/k⌋`.
#[derive(Debug, Clone, Copy)]
pub struct ScaleGalois {
/// Factor de escala, `k ≥ 1`.
pub k: u64,
}
impl ScaleGalois {
/// Crea una escala con `k ≥ 1` (satura en 1 si se pasa 0).
pub fn new(k: u64) -> Self {
Self { k: k.max(1) }
}
}
impl GaloisRestriction<Nat, Nat> for ScaleGalois {
fn lower(&self, a: &Nat) -> Nat {
Nat(a.0.saturating_mul(self.k))
}
fn upper(&self, b: &Nat) -> Nat {
Nat(b.0 / self.k)
}
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
/// Comprueba las cuatro leyes de retículo sobre una terna concreta.
fn leyes_de_reticulo<L: LatticeCell + std::fmt::Debug>(a: &L, b: &L, c: &L) {
// Conmutatividad.
assert_eq!(a.join(b), b.join(a));
assert_eq!(a.meet(b), b.meet(a));
// Asociatividad.
assert_eq!(a.join(b).join(c), a.join(&b.join(c)));
assert_eq!(a.meet(b).meet(c), a.meet(&b.meet(c)));
// Idempotencia.
assert_eq!(a.join(a), a.clone());
assert_eq!(a.meet(a), a.clone());
// Absorción.
assert_eq!(a.join(&a.meet(b)), a.clone());
assert_eq!(a.meet(&a.join(b)), a.clone());
// Neutros.
assert_eq!(a.join(&L::bottom()), a.clone());
assert_eq!(a.meet(&L::top()), a.clone());
}
fn nat_st() -> impl Strategy<Value = Nat> {
(0u64..10_000).prop_map(Nat)
}
fn reset_st() -> impl Strategy<Value = ResetVal> {
prop_oneof![
Just(ResetVal::Bottom),
Just(ResetVal::Top),
(0u64..4, -100i64..100)
.prop_map(|(generation, value)| ResetVal::Gen { generation, value }),
]
}
proptest! {
#[test]
fn nat_es_reticulo(a in nat_st(), b in nat_st(), c in nat_st()) {
leyes_de_reticulo(&a, &b, &c);
}
#[test]
fn resetval_es_reticulo(a in reset_st(), b in reset_st(), c in reset_st()) {
leyes_de_reticulo(&a, &b, &c);
}
/// Generaciones distintas colapsan: su join es y su meet es ⊥.
#[test]
fn reset_incomparable_colapsa(v1 in -100i64..100, v2 in -100i64..100) {
let a = ResetVal::Gen { generation: 0, value: v1 };
let b = ResetVal::Gen { generation: 1, value: v2 };
prop_assert!(a.partial_cmp(&b).is_none(), "gen distintas son incomparables");
prop_assert_eq!(a.join(&b), ResetVal::Top);
prop_assert_eq!(a.meet(&b), ResetVal::Bottom);
}
/// Ley adjunta de Galois para la identidad: trivial pero debe cumplirse.
#[test]
fn galois_identidad(a in nat_st(), b in nat_st()) {
let g = IdentityGalois;
prop_assert_eq!(g.lower(&a) <= b, a <= g.upper(&b));
}
/// Ley adjunta de Galois para la escala k·a ≤ b ⟺ a ≤ ⌊b/k⌋, y
/// monotonía de `lower`.
#[test]
fn galois_escala(a in 0u64..1000, a2 in 0u64..1000, b in 0u64..1000, k in 1u64..8) {
let g = ScaleGalois::new(k);
let (na, nb) = (Nat(a), Nat(b));
prop_assert_eq!(g.lower(&na) <= nb, na <= g.upper(&nb), "ley adjunta");
// Monotonía de lower: a ≤ a' ⟹ lower(a) ≤ lower(a').
let (lo, hi) = (Nat(a.min(a2)), Nat(a.max(a2)));
prop_assert!(g.lower(&lo) <= g.lower(&hi));
}
}
}
+1
View File
@@ -15,6 +15,7 @@ pub mod cell;
pub mod cohomology;
pub mod error;
pub mod gf2;
pub mod lattice;
pub mod linalg;
pub mod nerve;
pub mod oracle;