File size: 7,940 Bytes
bb2363d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 | //! **Acoplador Duplo Pensamento↔backbone** (v0.6.0 — "conexão bidirecional
//! e paralela modelo ↔ modelo" estendida ao treino conjunto).
//!
//! Envolve a `PonteDupla` (r-05: P.1 sem deadlock por potencial, P.2
//! simultaneidade medida, P.3 filas limitadas) num protocolo lag-1
//! síncrono entre os dois MODELOS:
//!
//! - **A = backbone (gerador NCM):** envia o estado `(a, b, c)` —
//! conhecimento (resumo do tronco), estado de inferência (ẑ corrompido)
//! e sonda do nível — JUNTO com a confiança calibrada da sua distribuição
//! preditiva (o feedback que treina o gate do Pensamento);
//! - **B = Pensamento:** envia a correção `δ` agregada das N tentativas
//! (o valor que o backbone injeta no condicionamento) JUNTO com o
//! auto-acordo das tentativas (estatística de diversidade — insumo do
//! autoajuste de N, lema do acordo par-a-par).
//!
//! Em CADA rodada as duas direções carregam cargas REAIS e a troca é
//! simultânea (P.2 herdado + medido aqui). O lag-1 é a consequência lógica
//! da simultaneidade: `δ_k` depende do estado `s_k`, que chega a B na mesma
//! rodada em que A recebe `δ_{k−1}` — causalidade por filtração
//! (δ_k é mensurável w.r.t. F_k = σ(s_0..s_k)).
//!
//! **Provas (r-24):**
//! - **AC-1 (causalidade do protocolo lag-1):** a sequência trocada é
//! `(s_k, δ_{k−1})`; δ_k = P(s_k) com P mensurável ⇒ nenhuma mensagem
//! contém informação do futuro (filtração crescente).
//! - **AC-2 (terminação):** potencial Φ = R − k (orçamento de rodadas) —
//! decresce estritamente; herdado de P.1/T-P4.
//! - **AC-3 (consistência valor↔grafo):** no TREINO conjunto, a correção
//! injetada no grafo de autograd é o MESMO tensor δ cujo valor cruza a
//! ponte (cópia destacada apenas para o protocolo/telemetria) — o
//! gradiente não precisa cruzar canais; mora no grafo. Em inferência,
//! o protocolo carrega tudo (sem gradientes).
use crate::ponte::{EstatisticasPonte, PonteDupla};
use std::time::Instant;
#[derive(Debug, Clone)]
pub struct EstadoModelo {
/// conhecimento: resumo do tronco (projetado em d_lat).
pub a: Vec<f32>,
/// estado de inferência corrente: ẑ_{ℓ−1} (possivelmente corrompido).
pub b: Vec<f32>,
/// sonda da requisição corrente (nível/consulta).
pub c: Vec<f32>,
/// confiança calibrada do NCM na predição corrente (feedback A→B).
pub confianca: f64,
pub nivel: usize,
pub passo: usize,
}
#[derive(Debug, Clone)]
pub struct Correcao {
/// correção agregada δ (d_lat) a injetar em ẑ.
pub delta: Vec<f32>,
/// auto-acordo par-a-par das N tentativas (diversidade p/ autoajuste).
pub acordo: f64,
/// confiança do verificador NCM na resposta agregada (B→A informativo).
pub confianca_agregada: f64,
pub iteracao: usize,
pub pesos: Vec<f64>,
}
#[derive(Debug, Clone, Default)]
pub struct TelemetriaAcoplador {
pub rodadas: u64,
pub timeouts: u64,
pub soma_norma_delta: f64,
pub soma_confianca: f64,
pub soma_acordo: f64,
pub maior_sobreposicao_us: u128,
pub tempo_total_ms: f64,
}
pub struct AcopladorDuplo {
pub ponte: PonteDupla,
correcao_anterior: Correcao,
pub telemetria: TelemetriaAcoplador,
}
impl AcopladorDuplo {
pub fn nova(capacidade: usize, timeout_ms: u64) -> Self {
AcopladorDuplo {
ponte: PonteDupla::nova(capacidade, timeout_ms),
correcao_anterior: Correcao {
delta: Vec::new(),
acordo: 0.0,
confianca_agregada: 0.0,
iteracao: 0,
pesos: Vec::new(),
},
telemetria: TelemetriaAcoplador::default(),
}
}
/// UMA rodada duplex: A→B estado (a,b,c,conf); B→A correção ANTERIOR.
/// Devolve a correção RECEBIDA (para o backbone aplicar/inspecionar).
pub fn rodada(&mut self, estado: EstadoModelo) -> Result<Correcao, String> {
let inicio = Instant::now();
// payload A→B: [a; b; c; conf] | payload B→A: [δ; acordo; conf_agr]
let mut carga_a: Vec<f32> = Vec::with_capacity(
estado.a.len() + estado.b.len() + estado.c.len() + 3,
);
carga_a.extend(estado.a.iter().copied());
carga_a.extend(estado.b.iter().copied());
carga_a.extend(estado.c.iter().copied());
carga_a.push(estado.confianca as f32);
let mut carga_b: Vec<f32> = self.correcao_anterior.delta.clone();
carga_b.push(self.correcao_anterior.acordo as f32);
carga_b.push(self.correcao_anterior.confianca_agregada as f32);
// troca simultânea (P.2) com timeout (P.1)
let (msg_de_b, _msg_de_a) = self.ponte.rodada(carga_a, carga_b, estado.nivel)?;
// reconstrói a correção recebida de B
let recebida = Correcao {
delta: msg_de_b.carga.clone(),
acordo: self.correcao_anterior.acordo,
confianca_agregada: self.correcao_anterior.confianca_agregada,
iteracao: self.correcao_anterior.iteracao,
pesos: self.correcao_anterior.pesos.clone(),
};
// telemetria
let norm: f64 = msg_de_b
.carga
.iter()
.map(|x| (*x as f64) * (*x as f64))
.sum::<f64>()
.sqrt();
let t = self.ponte.estatisticas();
self.telemetria.rodadas += 1;
self.telemetria.timeouts += t.quedas_por_timeout;
self.telemetria.soma_norma_delta += norm;
self.telemetria.soma_confianca += estado.confianca;
self.telemetria.soma_acordo += recebida.acordo;
self.telemetria.maior_sobreposicao_us =
self.telemetria.maior_sobreposicao_us.max(t.maior_sobreposicao_us);
self.telemetria.tempo_total_ms += inicio.elapsed().as_secs_f64() * 1000.0;
Ok(recebida)
}
/// Registra a correção fresca (calculada do estado recebido nesta
/// rodada) — será o payload B→A da PRÓXIMA rodada (lag-1 — AC-1).
pub fn registrar_correcao(&mut self, c: Correcao) {
self.correcao_anterior = c;
}
pub fn estatisticas(&self) -> EstatisticasPonte {
self.ponte.estatisticas()
}
pub fn encerrar(&self) {
self.ponte.encerrar();
}
}
pub fn versao() -> &'static str {
"acoplador-0.6.0"
}
#[cfg(test)]
mod testes {
use super::*;
#[test]
fn ac1_ac2_rodadas_causais_terminam() {
// 60 rodadas com estados crescentes — sem deadlock, sem timeout
let mut ac = AcopladorDuplo::nova(8, 500);
let mut conf = 0.1f64;
for k in 0..60 {
let estado = EstadoModelo {
a: vec![0.1 * k as f32; 8],
b: vec![0.2 * k as f32; 8],
c: vec![0.3; 4],
confianca: conf,
nivel: k % 7,
passo: k,
};
let recebida = ac.rodada(estado).expect("rodada sem deadlock");
assert!(recebida.delta.len() >= 2, "correção recebida não vazia após 1ª rodada");
// δ_k = função do estado s_k (causal): registra correção fresca
let delta: Vec<f32> = (0..8).map(|i| (i as f32 + k as f32) * 0.01).collect();
ac.registrar_correcao(Correcao {
delta,
acordo: 0.5,
confianca_agregada: conf,
iteracao: k,
pesos: vec![0.25; 4],
});
conf = (conf * 1.02).min(0.99);
}
let t = &ac.telemetria;
assert_eq!(t.rodadas, 60);
assert_eq!(t.timeouts, 0, "nenhum timeout");
assert!(t.soma_norma_delta > 0.0);
// P.2: sobreposição medida nas duas direções
let p = ac.estatisticas();
assert!(p.envios_a_para_b == 60 && p.envios_b_para_a == 60);
assert!(p.quedas_por_timeout == 0);
}
}
|