//! **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, /// estado de inferência corrente: ẑ_{ℓ−1} (possivelmente corrompido). pub b: Vec, /// sonda da requisição corrente (nível/consulta). pub c: Vec, /// 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, /// 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, } #[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 { let inicio = Instant::now(); // payload A→B: [a; b; c; conf] | payload B→A: [δ; acordo; conf_agr] let mut carga_a: Vec = 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 = 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::() .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 = (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); } }