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);
    }
}