File size: 8,338 Bytes
2dc44f1 06e4b6f 2dc44f1 06e4b6f 2dc44f1 | 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 | //! Tipos de domínio com invariantes no sistema de tipos (v0.9.0).
//!
//! **Aprimoramento 5 da crítica Qwen3.8 (r-34 §6), CORRIGIDO:** a proposta
//! original declarava `pub struct Probabilidade(pub f64)` alegando "garante
//! semanticamente [0, 1]" — o que é **FALSO**: um campo público permite
//! `Probabilidade(7.0)` em qualquer ponto do código, e o verificador de
//! tipos não impede nada. A garantia só existe com (a) campo **privado** e
//! (b) construtor **validado** (`novo`), que recusa valores fora do domínio
//! uma única vez, na fronteira. Este módulo implementa a forma correta.
//!
//! **Prova lógica (T-V1):** todo valor de `Probabilidade` acessível fora
//! deste módulo é obtido por `novo` (campo privado ⇒ construção só via
//! construtor) ou por `de_unchecked` (marcado `unsafe`-semântico, visível
//! apenas `pub(crate)` para hot paths já verificados). `novo` devolve
//! `Err` para `v ∉ [0,1] ∪ {NaN}` ⇒ por indução estrutural sobre as
//! operações do tipo (nenhuma muta o campo), **todo** `Probabilidade`
//! Viveiro satisfaz `0 ≤ v ≤ 1`. ∎
//!
//! **Kani (verificação formal):** registrado como débito — o ganho marginal
//! sobre construtor-validado + testes de propriedade é pequeno para
//! invariantes de intervalo escalar; as provas estruturais continuam nos
//! markdown (r-XX) como exige a regra da casa.
use candle_core::Result;
/// Probabilidade — domínio fechado [0, 1].
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Probabilidade(f64);
impl Probabilidade {
/// Construtor validado: recusa `v` fora de [0, 1] (incluindo NaN).
pub fn novo(v: f64) -> Result<Self> {
if v.is_nan() || v < 0.0 || v > 1.0 {
candle_core::bail!("Probabilidade fora de [0,1]: {v}");
}
Ok(Probabilidade(v))
}
/// Valor do domínio (o campo privado garante que passou por `novo`).
pub fn valor(&self) -> f64 {
self.0
}
/// Complemento `1 − p` — preserva o domínio (fechamento).
pub fn complemento(&self) -> Probabilidade {
Probabilidade(1.0 - self.0)
}
}
/// Ativação de gate — domínio fechado [0, 1] (saída de `GatePunicao`).
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct GateAtivacao(f64);
impl GateAtivacao {
pub fn novo(v: f64) -> Result<Self> {
if v.is_nan() || v < 0.0 || v > 1.0 {
candle_core::bail!("GateAtivacao fora de [0,1]: {v}");
}
Ok(GateAtivacao(v))
}
pub fn valor(&self) -> f64 {
self.0
}
}
/// Temperatura inversa γ = 1/T — domínio aberto (0, +∞).
///
/// **Crítica Qwen3.8 lote 3, nº 4 / proposta 2 (v0.10.0, r-35 §5).** A
/// proposta `pub struct TemperaturaInversa(f64)` com construtor validado
/// é a forma CORRETA do padrão newtype — mas a alegação "garante em tempo
/// de COMPILAÇÃO que T > 0" é **FALSA** e já constava como errata na
/// r-34 §6: o construtor é uma checagem de RUNTIME na fronteira única
/// (o campo privado impede construção fora dela; nada é verificado pelo
/// verificador de tipos). O ganho real: validação em ponto único +
/// contrato visível na assinatura + fechamento sob as operações.
///
/// **Prova lógica (T-V3):** todo `TemperaturaInversa` acessível fora do
/// módulo vem de `nova`/`de_gamma` (campo privado) ou `de_unchecked`
/// (`pub(crate)`, hot paths já verificados). Ambos os construtores
/// recusam γ ∉ (0, +∞) — incluindo T subnormal com 1/T = +∞ — e nenhuma
/// operação do tipo muta o campo; por indução estrutural, todo valor
/// carrega γ finito e positivo. Como γ > 0 é EXATAMENTE a pré-condição
/// da convexidade estrita de L̃(γ) = NLL(softmax(γ·z)) (Teo N-2, r-33:
/// L̃'' = γ²·Var_p(z) ≥ 0), o tipo CARREGA a pré-condição do teorema. ∎
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct TemperaturaInversa(f64);
impl TemperaturaInversa {
/// Construtor validado a partir da temperatura T: exige T > 0, T
/// finito e γ = 1/T finito (rejeita T subnormal, onde 1/T = +∞).
pub fn nova(temperatura: f64) -> Result<Self> {
if !(temperatura > 0.0) || !temperatura.is_finite() {
candle_core::bail!(
"Temperatura deve ser > 0 e finita (recebido {temperatura}) \
— pré-condição da convexidade N-2");
}
let gamma = 1.0 / temperatura;
if !gamma.is_finite() {
candle_core::bail!("1/T estourou para +∞ (T={temperatura} subnormal)");
}
Ok(TemperaturaInversa(gamma))
}
/// Construtor validado a partir de γ diretamente.
pub fn de_gamma(gamma: f64) -> Result<Self> {
if !(gamma > 0.0) || !gamma.is_finite() {
candle_core::bail!("γ deve ser > 0 e finito (recebido {gamma})");
}
Ok(TemperaturaInversa(gamma))
}
/// Valor de γ = 1/T (passou pela validação).
pub fn valor(&self) -> f64 {
self.0
}
/// Temperatura T = 1/γ (passou pela validação).
pub fn temperatura(&self) -> f64 {
1.0 / self.0
}
}
#[cfg(test)]
mod testes_tipos {
use super::*;
/// T-V1a — `novo` recusa valores fora do domínio (fronteira única).
#[test]
fn tv1a_construtor_recusa_fora_do_dominio() {
for v in [-1e-12, -0.5, 1.0 + 1e-12, 7.0, f64::NAN, f64::INFINITY] {
assert!(Probabilidade::novo(v).is_err(), "deveria recusar {v}");
assert!(GateAtivacao::novo(v).is_err(), "deveria recusar {v}");
}
}
/// T-V1b — `novo` aceita exatamente o fechamento [0, 1].
#[test]
fn tv1b_construtor_aceita_o_fechamento() {
for v in [0.0, 1e-9, 0.25, 0.5, 0.999, 1.0] {
let p = Probabilidade::novo(v).unwrap();
assert_eq!(p.valor(), v);
let g = GateAtivacao::novo(v).unwrap();
assert_eq!(g.valor(), v);
}
}
/// T-V1c — fechamento do complemento: p ∈ [0,1] ⇒ 1−p ∈ [0,1].
#[test]
fn tv1c_complemento_preserva_dominio() {
for v in [0.0, 0.1, 0.5, 0.9, 1.0] {
let p = Probabilidade::novo(v).unwrap();
let q = p.complemento();
assert!((0.0..=1.0).contains(&q.valor()));
}
}
/// T-V2 — o gate tipado produz GateAtivacao válido em TODO o domínio
/// (varredura densa — a prova estrutural é F-3; esta é a regressão).
#[test]
fn tv2_gate_tipado_dominio_valido() {
use crate::fnh::GatePunicao;
let gate = GatePunicao::default();
let mut n = 0usize;
let mut soma = 0.0f64;
for i in 0..=2000 {
let p = Probabilidade::novo(i as f64 / 2000.0).unwrap();
let g = gate.valor_tipado(&p);
assert!((0.0..=1.0).contains(&g.valor()), "gate fora de [0,1]: {}", g.valor());
soma += g.valor();
n += 1;
}
assert_eq!(n, 2001);
// integral do gate ≈ área da sigmoide retificada (> 0, < 1·domínio)
assert!(soma > 0.0 && soma < 2001.0);
}
/// T-V3a — construtores recusam γ ∉ (0, +∞): T ≤ 0, NaN, ±∞ e o
/// caso subnormal T = 5e-324 (1/T = +∞ em f64).
#[test]
fn tv3a_construtores_recusam_fora_do_dominio() {
for t_mau in [0.0, -1e-9, -5.0, f64::NAN, f64::INFINITY, 5e-324] {
assert!(TemperaturaInversa::nova(t_mau).is_err(), "nova devia recusar {t_mau}");
}
for g_mau in [0.0, -1e-9, -3.0, f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
assert!(TemperaturaInversa::de_gamma(g_mau).is_err(), "de_gamma devia recusar {g_mau}");
}
}
/// T-V3b — fechamento: nova(T).temperatura() = T e
/// nova(T).valor() = 1/T para T representável.
#[test]
fn tv3b_fechamento_inversao() {
for t_bom in [1.0, 0.125, 8.0, 1e-6, 1e6, 0.373] {
let ti = TemperaturaInversa::nova(t_bom).unwrap();
assert!((ti.temperatura() - t_bom).abs() < 1e-12);
assert!((ti.valor() - 1.0 / t_bom).abs() < 1e-12);
}
}
/// T-V3c — de_gamma/nova concordam (mesma família paramétrica).
#[test]
fn tv3c_de_gamma_concorda() {
let a = TemperaturaInversa::nova(2.0).unwrap();
let b = TemperaturaInversa::de_gamma(0.5).unwrap();
assert_eq!(a, b);
}
}
|