File size: 9,713 Bytes
06e4b6f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
//! `python::moe` — extraído do monólito na v0.10.0
//! (divisão SRP, crítica Qwen3.8 lote 3 nº 1; r-35 §2).

use super::*;


/// **MoE redirecionável — demo completa (M-R1..M-R5):** neutralidade da
/// atenção própria (M-R2), invariância de capacidade/perda auxiliar sob
/// redirecionamento (M-R1), paralelismo medido (M-R3), water-filling por
/// QE vs uniforme (M-R5) e fechamento da classe (M-R4 — via permutação).
#[pyfunction]
pub(crate) fn demo_moe_redirecionavel(d: usize, n: usize, semente: u64) -> PyResult<PyObject> {
    Python::with_gil(|py| {
        let dev = candle_core::Device::Cpu;
        crate::rng::definir_semente(semente);
        let mut rng = crate::rng::RngDeterministico::novo(semente);
        let x: Vec<f32> = (0..n * d).map(|_| rng.normal(0.0, 1.0) as f32).collect();
        let x = candle_core::Tensor::from_vec(x, (n, d), &dev)
            .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
        let mut moe = moe_redirecionavel::MoeRedirecionavel::novo(d, 8, 2, n, &dev)
            .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
        // M-R2: neutralidade no nascimento
        let g0 = moe.atencao.gate.as_tensor().to_scalar::<f32>().unwrap();
        let y0 = moe.atencao.fwd(&x).unwrap();
        let dif0 = (&y0 - &x).unwrap().sqr().unwrap().sum_all().unwrap()
            .to_scalar::<f32>().unwrap() as f64;
        // abre o gate (mecanismo ativo)
        moe.atencao.gate.set(&candle_core::Tensor::new(1.0f32, &dev).unwrap()).unwrap();
        // M-R1: invariância sob redirecionamento
        let s_enc = moe.forward(&x, moe_redirecionavel::Papel::Codificador).unwrap();
        let carga_antes: usize = s_enc.carga.iter().sum();
        let perda_antes = s_enc.perda_aux.to_scalar::<f32>().unwrap() as f64;
        let permuta: Vec<usize> = (0..8).map(|i| (i + 1) % 8).collect();
        moe.redirecionar(&permuta);
        let s_enc2 = moe.forward(&x, moe_redirecionavel::Papel::Codificador).unwrap();
        let carga_depois: usize = s_enc2.carga.iter().sum();
        let perda_depois = s_enc2.perda_aux.to_scalar::<f32>().unwrap() as f64;
        // M-R3: paralelismo medido (enc ∥ dec em 2 threads)
        let (_e, _dc, t_par, t_seq, speedup) = moe.fwd_ambos_par(&x).unwrap();
        // M-R5: water-filling vs uniforme (cota analítica)
        let qes: Vec<f64> = vec![0.15, 0.4, 1.0, 0.25, 0.6, 0.1, 0.35, 0.5];
        let c_wf = moe_redirecionavel::alocar_por_qe(&qes, 16);
        let c_un = vec![2usize; 8];
        let cota = |c: &[usize]| qes.iter().zip(c).map(|(q, ci)| q * q / *ci as f64).sum::<f64>();
        let (cota_wf, cota_un) = (cota(&c_wf), cota(&c_un));
        let d_ = serde_json::json!({
            "versao": moe_redirecionavel::versao(), "d": d, "n": n,
            "valor_gate_no_nascimento": g0, "neutralidade_dif": dif0,
            "carga_antes": carga_antes, "carga_depois": carga_depois,
            "perda_aux_antes": perda_antes, "perda_aux_depois": perda_depois,
            "t_paralelo_s": t_par, "t_sequencial_s": t_seq, "speedup": speedup,
            "alocacao_water_filling": c_wf, "cota_wf": cota_wf, "cota_uniforme": cota_un,
            "gates": {
                "gate_mr1_capacidade": carga_antes == carga_depois,
                "gate_mr1_perda_aux": (perda_antes - perda_depois).abs() < 2.0 + 1e-6,
                "gate_mr2_neutralidade": dif0 == 0.0 && g0 == 0.0,
                "gate_mr3_paralelismo": t_par < t_seq,
                "gate_mr5_water_filling": cota_wf <= cota_un + 1e-9,
            },
        });
        Ok(pythonize::pythonize(py, &d_)?.into())
    })
}


/// **JEPA com MoE redirecionável (ablação de 3 braços):** `modo` ∈
/// {"pequena", "densa", "moe"} — o braço "densa" tem a capacidade TOTAL dos
/// especialistas SEM roteamento (isola o ganho do roteamento em si).
#[pyfunction]
pub(crate) fn jepa_com_moe(
    patches: Vec<Vec<f32>>,
    modo: String,
    passos: usize,
    frac_mascara: f64,
    semente: u64,
) -> PyResult<PyObject> {
    Python::with_gil(|py| {
        let dev = candle_core::Device::Cpu;
        let t = tensor_f64_2d(&patches.into_iter().map(|l| l.into_iter().map(|x| x as f64).collect()).collect::<Vec<Vec<f64>>>(), &dev)
            .and_then(|t| t.to_dtype(candle_core::DType::F32))
            .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
        let modo: &'static str = match modo.as_str() {
            "pequena" => "pequena",
            "densa" => "densa",
            _ => "moe",
        };
        let r = moe_redirecionavel::treinar_jepa_com_moe(&t, modo, passos, frac_mascara, semente)
            .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
        let d_ = serde_json::json!({
            "modo": r.modo, "perda_pred_inicial": r.perda_pred_inicial,
            "perda_pred_final": r.perda_pred_final, "perda_vc_final": r.perda_vc_final,
            "std_emb": r.std_emb, "carga_max": r.carga_max, "descartados": r.descartados,
            "queda_abs": r.perda_pred_inicial - r.perda_pred_final,
            "aprendeu": r.perda_pred_final < r.perda_pred_inicial,
        });
        Ok(pythonize::pythonize(py, &d_)?.into())
    })
}


/// **SOM refinado por MoE com water-filling (M-R5) + redirecionamento
/// para decodificador:** treina um 4D-SOM, refina com MoE (QE′ < QE),
/// compara com alocação uniforme e mede a decodificação pós-redirect.
#[pyfunction]
pub(crate) fn som_com_moe(
    dados: Vec<Vec<f64>>,
    lado: usize,
    passos: usize,
    semente: u64,
) -> PyResult<PyObject> {
    Python::with_gil(|py| {
        let dev = candle_core::Device::Cpu;
        let t = tensor_f64_2d(&dados, &dev)
            .and_then(|t| t.to_dtype(candle_core::DType::F32))
            .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
        // treina o 4D-SOM (r-19: S4-1/S4-2/S4-3) — som4d opera em F32
        let cfg_som = som4d::CfgSom4D { nx: lado, ny: lado, nz: lado, nt: lado, ..Default::default() };
        let som = som4d::treinar_som4d(&t, &cfg_som, &dev)
            .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
        let centroides: Vec<Vec<f64>> = som.prototipos.clone();
        // refinamento MoE (mesmo tensor F32; centroides seguem em f64)
        let r = moe_redirecionavel::refinar_som_com_moe(&t, &centroides, passos, semente)
            .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
        let d_ = serde_json::json!({
            "qe_antes": r.qe_antes, "qe_depois_water_filling": r.qe_depois,
            "qe_depois_uniforme": r.qe_alocacao_uniforme,
            "carga_por_celula": r.carga_por_celula,
            "tempo_treino_s": r.tempo_treino_s,
            "mse_decodificador_pos_redirect": r.mse_decodificador,
            "reducao_qe_pct": 100.0 * (r.qe_antes - r.qe_depois) / r.qe_antes.max(1e-12),
            "gates": {
                "gate_moe_refina": r.qe_depois < r.qe_antes,
                "gate_wf_melhor_uniforme": r.qe_depois <= r.qe_alocacao_uniforme * 1.02,
                "gate_redirect_decodifica": r.mse_decodificador < r.qe_antes.powi(2),
            },
        });
        Ok(pythonize::pythonize(py, &d_)?.into())
    })
}


/// **AcopladorDuplo — demo do protocolo lag-1 síncrono** (AC-1/AC-2):
/// rodadas duplex Pensamento↔backbone com correções decaindo (co-adaptação)
/// e confiança subindo; zero deadlocks/timeouts (P.1/P.2 herdados).
#[pyfunction]
pub(crate) fn demo_acoplador_duplo(rodadas: usize, timeout_ms: u64) -> PyResult<PyObject> {
    Python::with_gil(|py| {
        let mut ac = acoplador::AcopladorDuplo::nova(16, timeout_ms);
        let mut rng = crate::rng::RngDeterministico::novo(77);
        let mut conf = 0.05f64;
        let mut traj = Vec::new();
        for k in 0..rodadas {
            let estado = acoplador::EstadoModelo {
                a: vec![0.1 * (k % 7) as f32; 16],
                b: vec![rng.normal(0.0, 0.3) as f32; 16],
                c: vec![0.05; 8],
                confianca: conf,
                nivel: k % 7,
                passo: k,
            };
            let _ = ac.rodada(estado).map_err(|e|
                pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
            // correção fresca: decaindo com a confiança (co-adaptação)
            let amplitude = (1.0 - conf).max(0.05);
            let delta: Vec<f32> = (0..16).map(|i| amplitude as f32 * ((i % 5) as f32 - 2.0) * 0.1).collect();
            ac.registrar_correcao(acoplador::Correcao {
                delta, acordo: 0.6 + 0.3 * conf, confianca_agregada: conf,
                iteracao: k, pesos: vec![0.25; 4],
            });
            traj.push(conf);
            conf = (conf + 0.04 + rng.uniforme() * 0.02).min(0.99);
        }
        let t = &ac.telemetria;
        let p = ac.estatisticas();
        let d_ = serde_json::json!({
            "versao": acoplador::versao(), "rodadas": t.rodadas,
            "timeouts": t.timeouts, "tempo_total_ms": t.tempo_total_ms,
            "soma_norma_delta": t.soma_norma_delta,
            "conf_final": traj.last(), "conf_trajetoria_len": traj.len(),
            "envios_a_para_b": p.envios_a_para_b, "envios_b_para_a": p.envios_b_para_a,
            "maior_sobreposicao_us": t.maior_sobreposicao_us,
            "gates": {
                "gate_terminou": t.rodadas == rodadas as u64,
                "gate_sem_timeout": t.timeouts == 0,
                "gate_duplex_simultaneo": p.envios_a_para_b == p.envios_b_para_a && p.envios_a_para_b == rodadas as u64,
            },
        });
        Ok(pythonize::pythonize(py, &d_)?.into())
    })
}