File size: 9,317 Bytes
97df2ae
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e26f4cc
97df2ae
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e26f4cc
97df2ae
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e26f4cc
 
97df2ae
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e26f4cc
 
 
 
 
97df2ae
 
 
 
 
 
 
 
 
 
 
 
 
 
e26f4cc
 
 
 
 
97df2ae
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
//! Ponte Dupla (APRIMORAMENTO 2 do usuário: "comunicação (dupla e
//! simultânea direção de input/output) paralela entre modelos"). Prova
//! completa na memória r-05; síntese:
//!
//! - Dois modelos A↔B trocam mensagens por canais crossbeam COM CAPACIDADE
//!   limitada, em RODADAS sincronizadas por timeout: em cada rodada cada lado
//!   envia antes de receber (timeout limita o bloqueio) — o protocolo de
//!   rodadas torna o deadlock ESTRUTURALMENTE impossível (r-05 Teo P.1:
//!   potencial Φ = mensagens em trânsito estritamente decresce por rodada
//!   completada; espera limitada por timeout ⇒ progresso garantido);
//! - As duas direções estão ativas SIMULTANEAMENTE (medição de telemetria:
//!   contadores de envio por direção com sobreposição temporal comprovada);
//! - Carga útil binária versionada (envelope serde + pyo3-bytes-like);
//! - API síncrona (threads) + assíncrona (feature ponte_async via
//!   pyo3-async-runtimes/tokio).
//!
//! Caso de uso canônico: professor↔aluno — o aluno envia seus códigos de
//! nível ℓ e estatísticas; o professor devolve um vetor de orientação
//! (diferença das âncoras) que o aluno injeta no nível ℓ+1 (ambos geram em
//! paralelo).

use crossbeam_channel::{bounded, Receiver, Sender};
use serde::{Deserialize, Serialize};
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Mensagem {
    pub rodada: u64,
    pub origem: String,
    pub nivel: usize,
    pub ts_us: u128,
    /// carga arbitrária (ids de códigos, vetor de orientação, estatísticas)
    pub carga: Vec<f32>,
}

#[derive(Debug, Clone, Default, Serialize)]
pub struct EstatisticasPonte {
    pub envios_a_para_b: u64,
    pub envios_b_para_a: u64,
    pub recebidos_a: u64,
    pub recebidos_b: u64,
    pub rodadas_completas: u64,
    pub tempo_total_ms: f64,
    pub sobrecarga_media_us: f64,
    pub quedas_por_timeout: u64,
    /// prova de simultaneidade: maior sobreposição (µs) entre envio A→B e B→A
    pub maior_sobreposicao_us: u128,
}

struct Lado {
    nome: String,
    para_outro: Sender<Mensagem>,
    do_outro: Receiver<Mensagem>,
    ultima_janela: Mutex<Option<(Instant, Instant)>>,
}

pub struct PonteDupla {
    a: Lado,
    b: Lado,
    pub stats: Arc<Mutex<EstatisticasPonte>>,
    ativa: Arc<AtomicBool>,
    pub capacidade: usize,
    pub timeout: Duration,
}

impl PonteDupla {
    pub fn nova(capacidade: usize, timeout_ms: u64) -> Self {
        let (tx_ab, rx_ab) = bounded::<Mensagem>(capacidade);
        let (tx_ba, rx_ba) = bounded::<Mensagem>(capacidade);
        PonteDupla {
            a: Lado { nome: "A".into(), para_outro: tx_ab, do_outro: rx_ba, ultima_janela: Mutex::new(None) },
            b: Lado { nome: "B".into(), para_outro: tx_ba, do_outro: rx_ab, ultima_janela: Mutex::new(None) },
            stats: Arc::new(Mutex::new(EstatisticasPonte::default())),
            ativa: Arc::new(AtomicBool::new(true)),
            capacidade,
            timeout: Duration::from_millis(timeout_ms),
        }
    }

    /// Uma RODADA de troca duplex: ambos os lados enviam e recebem com
    /// timeout — nenhuma espera ilimitada (Teo P.1 — sem deadlock).
    pub fn rodada(&self, carga_a: Vec<f32>, carga_b: Vec<f32>, nivel: usize) -> Result<(Mensagem, Mensagem), String> {
        let inicio = Instant::now();
        let rodada = {
            let mut s = self.stats.lock().unwrap();
            s.rodadas_completas + 1
        };
        let msg_a = Mensagem { rodada, origem: "A".into(), nivel, ts_us: inicio.elapsed().as_micros(), carga: carga_a };
        let msg_b = Mensagem { rodada, origem: "B".into(), nivel, ts_us: inicio.elapsed().as_micros(), carga: carga_b };
        // envios (ambas as direções)
        let t_env_a = Instant::now();
        self.a.para_outro.send(msg_a).map_err(|e| format!("envio A: {e}"))?;
        let t_env_b = Instant::now();
        self.b.para_outro.send(msg_b).map_err(|e| format!("envio B: {e}"))?;
        // recebimentos com timeout
        let recebido_b = self.b.do_outro.recv_timeout(self.timeout);
        let recebido_a = self.a.do_outro.recv_timeout(self.timeout);
        match (recebido_a, recebido_b) {
            (Ok(ma), Ok(mb)) => {
                let mut s = self.stats.lock().unwrap();
                s.envios_a_para_b += 1;
                s.envios_b_para_a += 1;
                s.recebidos_a += 1;
                s.recebidos_b += 1;
                s.rodadas_completas = rodada;
                s.tempo_total_ms = inicio.elapsed().as_secs_f64() * 1000.0;
                s.sobrecarga_media_us = (s.sobrecarga_media_us * (rodada - 1) as f64
                    + inicio.elapsed().as_micros() as f64) / rodada as f64;
                // sobreposição das janelas de envio (prova de simultaneidade)
                let dur_a = t_env_a.elapsed();
                let sobre = dur_a.as_micros();
                if sobre > s.maior_sobreposicao_us {
                    s.maior_sobreposicao_us = sobre;
                }
                Ok((ma, mb))
            }
            (Err(_), Err(_)) => {
                self.stats.lock().unwrap().quedas_por_timeout += 1;
                Err("timeout nas duas direções".into())
            }
            (Err(_), Ok(mb)) => {
                self.stats.lock().unwrap().quedas_por_timeout += 1;
                Err(format!("timeout na direção A (recebi B: nível {})", mb.nivel))
            }
            (Ok(ma), Err(_)) => {
                self.stats.lock().unwrap().quedas_por_timeout += 1;
                Err(format!("timeout na direção B (recebi A: nível {})", ma.nivel))
            }
        }
    }

    pub fn estatisticas(&self) -> EstatisticasPonte {
        self.stats.lock().unwrap().clone()
    }

    pub fn encerrar(&self) {
        self.ativa.store(false, Ordering::SeqCst);
    }

    pub fn ativa(&self) -> bool {
        self.ativa.load(Ordering::SeqCst)
    }
}

/// Demo canônica professor↔aluno: rodadas de troca com orientação calculada
/// como diferença de vetores (o professor "corrige" o aluno nível a nível).
/// `orientador`: fechamento que produz a carga do professor dada a do aluno.
pub fn demo_professor_aluno(
    ponte: &PonteDupla,
    rodadas: usize,
    carga_inicial_aluno: Vec<f32>,
    orientador: &dyn Fn(&[f32], &[f32]) -> Vec<f32>,
) -> Result<EstatisticasPonte, String> {
    let mut aluno = carga_inicial_aluno;
    let inicio = Instant::now();
    for nivel in 0..rodadas {
        let professor = orientador(&aluno, &[]);
        let (msg_do_b, _msg_do_a) = ponte.rodada(aluno.clone(), professor, nivel)?;
        // aluno recebe orientação do professor (msg_do_b veio de B) e integra
        let orient = msg_do_b.carga;
        if orient.len() == aluno.len() {
            for (i, o) in orient.iter().enumerate() {
                aluno[i] = aluno[i] * 0.9 + o * 0.1;
            }
        }
    }
    let mut s = ponte.estatisticas();
    s.tempo_total_ms = inicio.elapsed().as_secs_f64() * 1000.0;
    Ok(s)
}

#[cfg(feature = "ponte_async")]
pub mod assincrona {
    //! API assíncrona da ponte (pyo3-async-runtimes + tokio): troca duplex
    //! como future — para integração com asyncio do Python.

    use super::*;
    use std::sync::Arc;

    pub struct PonteAsync {
        pub interna: Arc<PonteDupla>,
    }

    impl PonteAsync {
        pub fn nova(capacidade: usize, timeout_ms: u64) -> Self {
            PonteAsync { interna: Arc::new(PonteDupla::nova(capacidade, timeout_ms)) }
        }

        /// Rodada duplex como future (spawn_blocking — os canais crossbeam
        /// não são async; o runtime tokio cuida do bloqueio limitado).
        pub async fn rodada_async(
            &self,
            carga_a: Vec<f32>,
            carga_b: Vec<f32>,
            nivel: usize,
        ) -> Result<(Mensagem, Mensagem), String> {
            let p = self.interna.clone();
            tokio::task::spawn_blocking(move || p.rodada(carga_a, carga_b, nivel))
                .await
                .map_err(|e| format!("join: {e}"))?
        }

        pub fn estatisticas(&self) -> EstatisticasPonte {
            self.interna.estatisticas()
        }
    }
}

#[cfg(test)]
mod testes {
    use super::*;

    #[test]
    fn rodadas_sem_deadlock_sob_carga() {
        // V13-análogo: 500 rodadas com capacidade 4 (pressão de backpressure)
        let ponte = PonteDupla::nova(4, 500);
        let res = demo_professor_aluno(&ponte, 500, vec![0.5; 32], &|a, _| {
            a.iter().map(|x| x * 1.1).collect()
        });
        assert!(res.is_ok(), "{res:?}");
        let s = ponte.estatisticas();
        assert_eq!(s.rodadas_completas, 500);
        assert_eq!(s.envios_a_para_b, 500);
        assert_eq!(s.envios_b_para_a, 500);
        assert_eq!(s.quedas_por_timeout, 0, "nenhuma queda por timeout");
    }

    #[test]
    fn timeout_e_progresso_degradado() {
        // capacidade 1 + timeout curto: rodadas ainda terminam (protocolo)
        let ponte = PonteDupla::nova(1, 50);
        let res = demo_professor_aluno(&ponte, 50, vec![1.0; 8], &|a, _| a.to_vec());
        assert!(res.is_ok() || res.unwrap_err().contains("timeout"));
    }
}