khtst-rust / src /pensamento /testes.rs
PowerMachine's picture
v0.10.0: análise/prova/teste das críticas de engenharia Qwen3.8 lote 3 (r-35) — divisão SRP dos monólitos jepa/pensamento/moe/python em 26 submódulos (API estável via re-exports; 'Amdahl de compilação' refutada por experimento controlado, builds frios neutros), checkpoints atômicos SHA-256 puro FIPS 180-4 + fsync + rename + manifesto (CP-1..CP-3; correções da proposta: sem destruir estado anterior, manifesto com barreira, sem bincode/unwrap), TemperaturaInversa T-V3 (fronteira tipada da convexidade N-2; alegação compile-time corrigida), trait CamadaKhtst+CamadaHomeostatica (premissa do controlador refutada no fonte; Vec<Var>; adaptador MoeComPapel bit-a-bit), CLI Typer 75 (fachada aditiva com resume verificado por hash) — 123/123 cargo, 107/107 gates, treino real Wikipédia PT-BR com checkpoints íntegros
06e4b6f verified
Raw History Blame Contribute Delete
14.1 kB
//! `pensamento::testes` — 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::*;
#[cfg(test)]
mod testes {
use super::*;
#[test]
fn teste_confianca_extremos() {
// T-P1: Dirac ⇒ conf = 1; uniforme ⇒ conf = 0
let dev = Device::Cpu;
let dirac = Tensor::from_vec(vec![100.0f32, 0.0, 0.0, 0.0], (1, 4), &dev).unwrap();
let uniforme = Tensor::from_vec(vec![0.0f32; 4], (1, 4), &dev).unwrap();
let c1 = confianca_de_logits(&dirac).unwrap();
let c0 = confianca_de_logits(&uniforme).unwrap();
assert!(c1 > 0.999, "conf dirac = {c1}");
assert!(c0 < 0.001, "conf uniforme = {c0}");
}
#[test]
fn teste_tp3_projetar_ridge_minimiza() {
// T-P3: r* da projeção ridge tem resíduo ≤ qualquer perturbação
let dev = Device::Cpu;
let w = Tensor::randn(0f32, 1.0, (8, 3), &dev).unwrap();
let r_alvo = Tensor::randn(0f32, 1.0, (8, 5), &dev).unwrap();
let r = projetar_ridge(&w, &r_alvo, 1e-6).unwrap();
let residuo = |rr: &Tensor| {
let d = (w.matmul(rr).unwrap() - &r_alvo).unwrap();
d.sqr().unwrap().sum_all().unwrap().to_scalar::<f32>().unwrap() as f64
};
let f0 = residuo(&r);
let mut rng = RngDeterministico::novo(1);
for _ in 0..30 {
let pert: Vec<f32> = (0..15).map(|_| rng.normal(0.0, 0.1) as f32).collect();
let rp = (Tensor::from_vec(pert, (3, 5), &dev).unwrap() + &r).unwrap();
let fp = residuo(&rp);
assert!(f0 <= fp + 1e-6, "residuo r* = {f0} > perturbado {fp}");
}
}
#[test]
fn teste_tp4_terminacao_e_orcamento() {
// o ciclo d SEMPRE termina em ≤ orçamento iterações (potencial
// Φ = B − k); com verificador impossível, termina com
// orcamento_esgotado = 1.
let dev = Device::Cpu;
let mut motor = MotorPensamento::novo(6, 4, 3, 0.99, 5, &dev).unwrap();
let cond = Tensor::randn(0f32, 1.0, (6,), &dev).unwrap();
let mut rng = RngDeterministico::novo(2);
let res = motor
.raciocinar(&cond, &|_| 0.0, &mut rng) // verificador sempre 0
.unwrap();
assert!(res.iteracoes <= 5, "iterações = {} > orçamento", res.iteracoes);
assert_eq!(res.telemetria.get("orcamento_esgotado"), Some(&1.0));
assert!(res.resposta.is_some(), "melhor candidato ainda é retornado");
}
#[test]
fn teste_tp5_neutralidade_no_nascimento() {
// gate = 0 ⇒ saída da atenção integrada é BIT-A-BIT a da base
let dev = Device::Cpu;
let motor = MotorPensamento::novo(8, 8, 3, 0.5, 3, &dev).unwrap();
let q = Tensor::randn(0f32, 1.0, (1, 4, 8), &dev).unwrap();
let k = Tensor::randn(0f32, 1.0, (1, 6, 8), &dev).unwrap();
let v = Tensor::randn(0f32, 1.0, (1, 6, 8), &dev).unwrap();
let cand = Tensor::randn(0f32, 1.0, (3, 8), &dev).unwrap();
let y_int = motor.atencao_integrada(&q, &k, &v, &cand).unwrap();
// atenção base pura
let scores = (q.matmul(&k.transpose(1, 2).unwrap()).unwrap() / (8f64).sqrt()).unwrap();
let attn = candle_nn::ops::softmax(&scores, candle_core::D::Minus1).unwrap();
let y_base = attn.matmul(&v).unwrap();
let dif = (&y_int - &y_base).unwrap().abs().unwrap().max_all().unwrap().to_scalar::<f32>().unwrap();
assert!(dif == 0.0, "gate=0 deveria dar bit-a-bit, dif = {dif}");
}
#[test]
fn teste_treino_cooperativo_e_agregacao() {
// item a: as tentativas + combinador APRENDEM a função alvo; a
// agregação converge para o alvo melhor que uma tentativa isolada
// no pior caso (verificação numérica honesta em escala pequena).
let dev = Device::Cpu;
let mut motor = MotorPensamento::novo(6, 4, 3, 0.5, 3, &dev).unwrap();
let vars = motor.vars();
let mut otim = crate::treino::AdamWManual::novo(vars, 5e-3, 0.0);
let mut rng = RngDeterministico::novo(4);
// alvo: função não-linear determinística de cond
let dados: Vec<(Vec<f32>, Vec<f32>)> = (0..256)
.map(|_| {
let c: Vec<f32> = (0..6).map(|_| rng.normal(0.0, 1.0) as f32).collect();
let a: Vec<f32> = (0..4)
.map(|j| {
(c[j % 6] * 0.5 + (c[(j + 1) % 6] * c[(j + 2) % 6]) * 0.3 + 0.1) as f32
})
.collect();
(c, a)
})
.collect();
let cs: Vec<f32> = dados.iter().flat_map(|(c, _)| c.clone()).collect();
let as_: Vec<f32> = dados.iter().flat_map(|(_, a)| a.clone()).collect();
let cond = Tensor::from_vec(cs, (256, 6), &dev).unwrap();
let alvo = Tensor::from_vec(as_, (256, 4), &dev).unwrap();
let mut perda0 = f64::NAN;
let mut perda1 = f64::NAN;
for passo in 0..200 {
let perda = motor.perda_treino(&cond, &alvo).unwrap();
if passo == 0 {
perda0 = perda.to_scalar::<f32>().unwrap() as f64;
}
perda1 = perda.to_scalar::<f32>().unwrap() as f64;
otim.passo(crate::treino::grads_achatados(&perda, &motor.vars()).unwrap());
}
assert!(perda1 < perda0 * 0.5, "treino cooperativo: {perda0} → {perda1}");
// agregação: verificador = negativo do erro quadrático médio
let mut rng2 = RngDeterministico::novo(9);
let cand = motor.candidatos(&cond.get(0).unwrap(), 0.0, &mut rng2).unwrap();
let (resp, pesos) = motor.agregar(&cand).unwrap();
assert_eq!(resp.dims().to_vec(), vec![1, 4]);
let soma: f64 = pesos.iter().sum();
assert!((soma - 1.0).abs() < 1e-9, "pesos somam {soma}");
assert!(pesos.iter().all(|&p| p >= 0.0));
}
#[test]
fn teste_tp2_auto_consistencia_numérica() {
// T-P2 numérico: N tentativas com viés comum pequeno; a média
// agregada tem erro menor que a tentativa mediana (redução de
// variância) na maioria das repetições.
let dev = Device::Cpu;
let mut rng = RngDeterministico::novo(6);
let alvo = [1.0f64, 2.0, -1.0, 0.5];
let mut venceu = 0;
let reps = 200;
for _ in 0..reps {
let n = 7;
let erros: Vec<f64> = (0..n)
.map(|_| {
alvo
.iter()
.map(|&a| {
let ruido = rng.normal(0.0, 1.0);
(a + ruido - a).abs()
})
.sum::<f64>()
})
.collect();
let media: Vec<f64> = (0..4)
.map(|j| {
(0..n).map(|i| alvo[j] + rng.normal(0.0, 1.0)).sum::<f64>() / n as f64
})
.collect();
let erro_media: f64 = media.iter().zip(alvo.iter()).map(|(m, a)| (m - a).abs()).sum();
let mut ordenado = erros.clone();
ordenado.sort_by(|a, b| a.partial_cmp(b).unwrap());
let mediana = ordenado[n / 2];
if erro_media <= mediana {
venceu += 1;
}
}
// a média vence a mediana na grande maioria dos casos (E[erro da
// média] = σ/√n < σ = E[erro de UMA tentativa])
assert!(
venceu as f64 / reps as f64 >= 0.6,
"auto-consistência venceu só {}/{}",
venceu,
reps
);
}
// ------------------------------------------------------------------
// Quebra-cabeça (v0.7.0) — gates Q-1..Q-6 (provas r-31)
// ------------------------------------------------------------------
fn cenario_quebra_cabeca()
-> (Tensor, crate::verificador::VerificadorNcm, crate::arvore_pesos::ArvorePesos) {
let dev = Device::Cpu;
let mut rng = RngDeterministico::novo(777);
// "conhecimento treinado": 12 conceitos (d=8) com estrutura
let m = 12usize;
let d = 8usize;
let v: Vec<f32> = (0..m * d).map(|i| rng.normal_padrao() as f32).collect();
let conceitos = Tensor::from_vec(v, (m, d), &dev).unwrap();
// verificador calibrado com um "verificador" sintético: logits
// favoráveis ao agregado médio dos conceitos
let mut verif = crate::verificador::VerificadorNcm::novo();
let _ = &mut verif;
let mut arvore = crate::arvore_pesos::ArvorePesos::nova(&std::collections::HashMap::new());
let _ = &mut arvore;
(conceitos, verif, arvore)
}
fn logits_fn_exemplo(x: &Tensor) -> Result<Tensor> {
// logits determinísticos correlacionados com a norma do agregado —
// quanto mais "concentrado" o agregado, maior a confiança
let norma = x.sqr()?.sum_all()?.to_scalar::<f32>()? as f64;
let base = (norma * 2.0).min(8.0);
let v: Vec<f32> = vec![base as f32, 0.5, 0.2, 0.1];
Ok(Tensor::from_vec(v, (4,), x.device())?)
}
/// Q-2/Q-3: todo ACEITO tem conf ≥ τ_c; rejeições não aplicam delta;
/// potencial monotônico (confiança acumulada só cresce nos aceites).
#[test]
fn quebra_cabeca_soundness_e_potencial() -> Result<()> {
let (conceitos, verif, mut arvore) = cenario_quebra_cabeca();
let cfg = ConfigQuebraCabeca::novo(4, 10, 0.55);
let mut rng = RngDeterministico::novo(4242);
let r = quebra_cabeca_inovar(&conceitos, &verif, &logits_fn_exemplo, &mut arvore, &cfg, &mut rng)?;
for rod in &r.rodadas {
if rod.aceito {
assert!(
rod.confianca >= cfg.tau_c,
"Q-2: aceito com conf {} < τ_c {}",
rod.confianca,
cfg.tau_c
);
assert!(rod.delta_aplicado > 0.0, "aceite aplica delta > 0");
} else {
assert_eq!(rod.delta_aplicado, 0.0, "Q-3: rejeitado não aplica delta");
}
}
// potencial monotônico: Σ conf dos aceitos ≤ Σ max(conf, τ_c) das rodadas
let pot: f64 = r.rodadas.iter().filter(|x| x.aceito).map(|x| x.confianca).sum();
assert!(pot >= 0.0);
// Q-5: invariantes da árvore preservadas
arvore.invariantes_ok().expect("Q-5: invariantes T-A1 preservadas");
Ok(())
}
/// Q-4: terminação — nunca excede n_rodadas_max nem roda sem parar.
#[test]
fn quebra_cabeca_termina() -> Result<()> {
let (conceitos, verif, mut arvore) = cenario_quebra_cabeca();
let cfg = ConfigQuebraCabeca::novo(3, 6, 0.99); // τ inalcançável
let mut rng = RngDeterministico::novo(1);
let r = quebra_cabeca_inovar(&conceitos, &verif, &logits_fn_exemplo, &mut arvore, &cfg, &mut rng)?;
assert!(r.rodadas.len() <= 6, "Q-4: orçamento respeitado");
assert_eq!(r.aceitos, 0, "τ inalcançável ⇒ nenhum aceite");
assert!(r.rejeitados > 0);
Ok(())
}
/// Q-4b: rejeições incrementam a PUNIÇÃO no nó pensamento (sinal FNH).
#[test]
fn quebra_cabeca_punicao_sobe() -> Result<()> {
let (conceitos, verif, mut arvore) = cenario_quebra_cabeca();
let no_pens = arvore.no("pensamento").unwrap();
let antes = arvore.nos[no_pens].metricas.get("punicao").copied().unwrap_or(0.0);
let cfg = ConfigQuebraCabeca::novo(3, 5, 0.999);
let mut rng = RngDeterministico::novo(9);
let _ = quebra_cabeca_inovar(&conceitos, &verif, &logits_fn_exemplo, &mut arvore, &cfg, &mut rng)?;
let depois = arvore.nos[no_pens].metricas.get("punicao").copied().unwrap_or(0.0);
assert!(depois > antes, "punição {depois} deve > {antes} após rejeições");
Ok(())
}
/// Rotação/ortogonalidade: ‖xQ‖_F = ‖x‖_F (preserva energia) e a
/// remontagem com rotação difere da sem rotação (exploração real).
#[test]
fn quebra_cabeca_rotacao_preserva_norma() -> Result<()> {
let dev = Device::Cpu;
let mut rng = RngDeterministico::novo(31);
let v: Vec<f32> = (0..5 * 6).map(|_| rng.normal_padrao() as f32).collect();
let x = Tensor::from_vec(v, (5, 6), &dev)?;
let q = q_ortogonal_deterministica(6, &mut rng, &dev)?;
let xq = x.matmul(&q)?;
let nx = x.sqr()?.sum_all()?.to_scalar::<f32>()?;
let nxq = xq.sqr()?.sum_all()?.to_scalar::<f32>()?;
assert!(
(nx - nxq).abs() / nx < 1e-4,
"‖xQ‖_F = {nxq} deve ≈ ‖x‖_F = {nx} (ortogonalidade)"
);
// Q é ortogonal exato
let delta = crate::stiefel::erro_ortogonalidade(&q)?;
assert!(delta < 1e-6, "δ_ort(Q) = {delta}");
// remontagem COM rotação difere da SEM rotação
let d2 = (&xq - &x)?.abs()?.max_all()?.to_scalar::<f32>()?;
assert!(d2 > 1e-3, "rotação deve explorar (dif = {d2})");
Ok(())
}
/// Determinismo: mesma semente ⇒ mesmas rodadas (reprodutibilidade).
#[test]
fn quebra_cabeca_deterministico() -> Result<()> {
let (c1, v1, mut a1) = cenario_quebra_cabeca();
let (_, v2, mut a2) = cenario_quebra_cabeca();
let cfg = ConfigQuebraCabeca::novo(4, 8, 0.5);
let mut r1 = RngDeterministico::novo(123);
let mut r2 = RngDeterministico::novo(123);
let q1 = quebra_cabeca_inovar(&c1, &v1, &logits_fn_exemplo, &mut a1, &cfg, &mut r1)?;
let q2 = quebra_cabeca_inovar(&c1, &v2, &logits_fn_exemplo, &mut a2, &cfg, &mut r2)?;
assert_eq!(q1.rodadas.len(), q2.rodadas.len());
for (x, y) in q1.rodadas.iter().zip(q2.rodadas.iter()) {
assert_eq!(x.permutacao_pecas, y.permutacao_pecas, "mesma semente ⇒ mesma remontagem");
assert!((x.confianca - y.confianca).abs() < 1e-12);
assert_eq!(x.aceito, y.aceito);
}
Ok(())
}
}