File size: 4,032 Bytes
f4c204d
e4ee5c4
 
f4c204d
 
 
 
e4ee5c4
f4c204d
 
 
 
 
 
 
e4ee5c4
f4c204d
 
 
 
 
 
 
 
 
 
e4ee5c4
f4c204d
e4ee5c4
f4c204d
 
 
 
 
 
e4ee5c4
 
 
f4c204d
e4ee5c4
 
 
 
 
 
 
 
f4c204d
e4ee5c4
f4c204d
 
 
e4ee5c4
 
 
 
 
 
 
 
f4c204d
 
e4ee5c4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
//! PROVAS EXECUTÁVEIS (gates de integração) do KHTST_RUST v0.7.0.
//!
//! Regra da casa (r-08 §1): *contar a estrutura não é verificar a função* —
//! toda verificação precisa de um `assert` que FALHE se o invariante quebrar.
//! Os 54 testes unitários vivem inline em cada módulo; ESTE arquivo contém os
//! GATES DE INTEGRAÇÃO entre módulos (vazamentos, paridades, neutralidades
//! e os gates novos da v0.7.0: FNH, árvore de pesos, quebra-cabeça, NLP/NLG).
//!
//! Histórico: os gates G1/G2/G4/G10 da v0.3.0 migraram para a suíte Python
//! (V4 máscara 0 vazamentos, V10 paridade NFE=L) e para testes inline dos
//! módulos (ssd, quantizador, mascaras); os gates G9/G12 da mistura de planos
//! tornaram-se obsoletos com a remoção da CabecaMistura (v0.4.0 — a mistura
//! vive hoje no verificador, ver F_mistura_verificador.md e verificador.rs).
//!
//! | gate | o que prova | módulos |
//! |---|---|---|
//! | G5  | nascimento neutro do FiLM (γ=β=ξ=ζ=0 ⇒ identidade) | gerador+tronco |
//! | M8  | PCGrad não-piora (⟨g_comb, g_i⟩ ≥ 0 ∀i) | perdas+treino |
//! | G13 | FNH: R∈SO(n) exato + neutralidade com punição baixa | fnh |
//! | G14 | FNH: gate de punição monotoneidade + migração de escala idempotente | fnh |
//! | G15 | árvore de pesos: partição de índices + simetria das arestas duplas | arvore_pesos |
//! | G16 | árvore: agregação fundo-topo = soma global (indução na árvore) | arvore_pesos |
//! | G17 | quebra-cabeça: todo aceito tem confiança ≥ τ_c (soundness V-2) | pensamento+verificador |
//! | G18 | NLP/NLG: nascimento neutro (Δ=0 bit-a-bit) | nlp+nlg |
//! | G19 | decodificador especulativo: inocuidade (distribuição preservada) | nlg |
//! | G20 | rotação no Pensamento: ortogonalidade preservada pós-ciclo | pensamento+stiefel |

use candle_core::{Device, Result, Tensor};

/// G5 — NASCIMENTO NEUTRO: com o tronco recém-construído (γ=0, β=0, ξ=0,
/// ζ=0), aplicar_film é a IDENTIDADE bit-a-bit, qualquer que seja o resumo.
#[test]
fn gate_g5_nascimento_neutro_film() -> Result<()> {
    use khtst_rust::config::ConfigKhtst;
    use khtst_rust::gerador::GeradorConceitos;
    let mut cfg = ConfigKhtst::default();
    cfg.modelo.vocab = 64;
    cfg.modelo.d_modelo = 32;
    cfg.modelo.n_camadas = 2;
    cfg.modelo.n_cabecas = 4;
    cfg.modelo.d_ff = 64;
    cfg.modelo.comprimento_ctx = 8;
    cfg.ncm.niveis = vec![2, 4, 8];
    cfg.ncm.d_lat = 8;
    cfg.ncm.k_por_nivel = 8;
    cfg.ncm.ativo = true;
    cfg.modelo.mtp.ativo = false;
    cfg.validar().expect("config do gate G5");
    let dev = Device::Cpu;
    let ger = GeradorConceitos::novo(&cfg, &dev)?;
    let x = Tensor::randn(0f32, 1f32, (2, 4, cfg.modelo.d_modelo), &dev)?;
    let resumo = Tensor::randn(0f32, 1f32, (2, cfg.modelo.d_modelo), &dev)?;
    for nivel in 1..=cfg.l_niveis() {
        let y = ger.tronco.aplicar_film(&x, &resumo, nivel)?;
        let dif = (&x - &y)?.abs()?.max_all()?.to_scalar::<f32>()?;
        assert_eq!(dif, 0.0, "G5: FiLM deve ser identidade no nascimento (nível {nivel})");
    }
    Ok(())
}

/// M8 — PCGrad projeta contra os gradientes ORIGINAIS (invariante de
/// não-piora: ⟨g_combinado, g_i⟩ ≥ 0 para toda perda i).
#[test]
fn gate_m8_pcgrad_nao_piora() {
    let g1 = vec![Some(vec![1.0f32]), Some(vec![-2.0])];
    let g2 = vec![Some(vec![-1.0f32]), Some(vec![0.5])];
    let combinado = khtst_rust::treino::pcgrad_achatado(vec![g1.clone(), g2.clone()]);
    let ip = |a: &Vec<Option<Vec<f32>>>, b: &Vec<Option<Vec<f32>>>| -> f64 {
        let mut s = 0f64;
        for (x, y) in a.iter().zip(b.iter()) {
            if let (Some(x), Some(y)) = (x, y) {
                s += x.iter().zip(y.iter()).map(|(p, q)| *p as f64 * *q as f64).sum::<f64>();
            }
        }
        s
    };
    let ip1 = ip(&combinado, &g1);
    let ip2 = ip(&combinado, &g2);
    assert!(ip1 >= -1e-9, "⟨g, g1⟩ = {ip1} ≥ 0 (não-piora)");
    assert!(ip2 >= -1e-9, "⟨g, g2⟩ = {ip2} ≥ 0 (não-piora)");
}