//! `cell` — el dato local que sabe fundirse y proyectarse (§6.1). //! //! Dos CRDTs con semántica controlada a mano (§10): un monótono (`GCounter`) y //! uno no monótono (`ResettableRegister`), que son las dos anclas del hito M1. use crate::linalg::Scalar; /// Un pedacito de estado local que sabe cómo intentar fundirse con otro, /// y cómo proyectarse al espacio de una arista compartida. pub trait Cell: Clone + PartialEq { /// El vector que representa este estado en el espacio de la stalk. fn to_vector(&self) -> Vec; /// Fusión de retículo (join). Debe ser conmutativa, asociativa, idempotente. fn join(&self, other: &Self) -> Self; /// ¿Esta operación es monótona? (marca para el oráculo CALM) fn is_monotone() -> bool; } /// CRDT mínimo del MVP: cuenta por réplica, fusiona por máximo. Siempre monótono. /// /// Cada réplica solo incrementa su propia casilla; fusionar = máximo casilla por /// casilla. Nunca hay conflicto porque nunca se resta. Embebe en `ℤⁿ` (§2.3). #[derive(Debug, Clone, PartialEq, Eq)] pub struct GCounter { /// Una casilla por réplica. pub slots: Vec, } impl GCounter { /// G-Counter en cero para `n` réplicas. pub fn zeros(n: usize) -> Self { Self { slots: vec![0; n] } } /// La réplica `i` incrementa su propia casilla en `by`. pub fn increment(&mut self, i: usize, by: u64) { self.slots[i] += by; } /// Valor total del contador (suma de casillas). pub fn value(&self) -> u64 { self.slots.iter().sum() } } impl Cell for GCounter { fn to_vector(&self) -> Vec { self.slots.iter().map(|&s| s as Scalar).collect() } fn join(&self, other: &Self) -> Self { let n = self.slots.len().max(other.slots.len()); let slots = (0..n) .map(|i| { let a = self.slots.get(i).copied().unwrap_or(0); let b = other.slots.get(i).copied().unwrap_or(0); a.max(b) }) .collect(); Self { slots } } fn is_monotone() -> bool { true } } /// CRDT no monótono para el caso "difícil": permite reset/borrado. /// /// El registro guarda un valor y un contador de generación. Un `reset` /// incrementa la generación y puede **bajar** el valor: eso retracta conocimiento /// (§glosario), luego NO es monótono. Sirve para provocar `H¹ ≠ 0` (§8, corrida B). #[derive(Debug, Clone, PartialEq, Eq)] pub struct ResettableRegister { /// Valor actual del registro. pub value: i64, /// Contador de generación; un reset lo incrementa (rompe la monotonicidad). pub generation: u64, } impl ResettableRegister { /// Registro inicial con `value` en la generación 0. pub fn new(value: i64) -> Self { Self { value, generation: 0, } } /// Sobrescribe el valor sin cambiar de generación (operación monótona-ish). pub fn set(&mut self, value: i64) { self.value = value; } /// Resetea: nueva generación con un valor que puede ser menor. NO monótono. pub fn reset(&mut self, value: i64) { self.generation += 1; self.value = value; } } impl Cell for ResettableRegister { fn to_vector(&self) -> Vec { vec![self.generation as Scalar, self.value as Scalar] } /// Gana la generación más alta; a igual generación, el mayor valor. fn join(&self, other: &Self) -> Self { match self.generation.cmp(&other.generation) { std::cmp::Ordering::Greater => self.clone(), std::cmp::Ordering::Less => other.clone(), std::cmp::Ordering::Equal => Self { generation: self.generation, value: self.value.max(other.value), }, } } fn is_monotone() -> bool { false } } #[cfg(test)] mod tests { use super::*; #[test] fn gcounter_join_es_maximo_y_monotono() { let a = GCounter { slots: vec![3, 0, 1] }; let b = GCounter { slots: vec![1, 5, 1] }; let j = a.join(&b); assert_eq!(j.slots, vec![3, 5, 1]); // Idempotente y conmutativo. assert_eq!(a.join(&a), a); assert_eq!(a.join(&b), b.join(&a)); // El join nunca baja respecto a ninguno de los dos (monotonía). assert!(j.value() >= a.value() && j.value() >= b.value()); assert!(GCounter::is_monotone()); } #[test] fn resettable_no_es_monotono() { let mut r = ResettableRegister::new(10); let alto = r.clone(); r.reset(0); // nueva generación, valor MENOR: retracta conocimiento. let fundido = alto.join(&r); // El reset (generación mayor) gana pese a tener valor menor → no monótono. assert_eq!(fundido.value, 0); assert_eq!(fundido.generation, 1); assert!(!ResettableRegister::is_monotone()); } }