File size: 5,427 Bytes
97df2ae e26f4cc 97df2ae e26f4cc 97df2ae e26f4cc 97df2ae e26f4cc 97df2ae e26f4cc 97df2ae e26f4cc 97df2ae e26f4cc 97df2ae | 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 | //! Máscaras e interpolação multiscale (port de `conceitos/mascaras.py` do n-04 §2.2;
//! contratos dos Teos D.1(ii)/B.2 — verificação V4: vazamento zero).
use candle_core::{D, DType, Device, Result, Tensor};
/// Máscara aditiva block-causal B (Teo D.1(ii)): o slot do bloco ℓ atende ao
/// prefixo (sempre visível) e a TODOS os blocos ≤ ℓ (bidirecional no bloco
/// próprio). `tamanhos` = [n_1, …, n_L]; `n_prefixo` = |cond|.
///
/// Retorna matriz (T+P, T+P) f32 com 0 onde permitido e f32::MIN onde bloqueado,
/// pronta para `scores + mask` (broadcast).
pub fn mascara_block_causal(tamanhos: &[usize], n_prefixo: usize, dev: &Device) -> Result<Tensor> {
let mut cortes = vec![n_prefixo];
for &t in tamanhos {
let ult = *cortes.last().unwrap();
cortes.push(ult + t);
}
let total = *cortes.last().unwrap();
let mut m = vec![f32::MIN; total * total];
for (bloco_i, (&ini_i, &fim_i)) in cortes.iter().zip(cortes.iter().skip(1)).enumerate() {
for i in ini_i..fim_i {
// prefixo sempre visível
for j in 0..n_prefixo {
m[i * total + j] = 0.0;
}
// blocos ≤ bloco_i inteiros visíveis
let fim_visivel = cortes[bloco_i + 1];
for j in 0..fim_visivel {
m[i * total + j] = 0.0;
}
}
}
Tensor::from_vec(m, (total, total), dev)?.to_dtype(DType::F32)
}
/// Verificação V4 — nenhuma posição do bloco ℓ acessa bloco > ℓ.
/// Devolve o número de vazamentos (deve ser 0).
pub fn contar_vazamentos(tamanhos: &[usize], n_prefixo: usize, mascara: &Tensor) -> Result<usize> {
let total = mascara.dim(0)?;
let dados = mascara.flatten_all()?.to_vec1::<f32>()?;
let mut cortes = vec![n_prefixo];
for &t in tamanhos {
let ult = *cortes.last().unwrap();
cortes.push(ult + t);
}
let mut vazamentos = 0;
for (bloco_i, (&ini_i, &fim_i)) in cortes.iter().zip(cortes.iter().skip(1)).enumerate() {
let inicio_proibido = cortes.get(bloco_i + 2).copied().unwrap_or(*cortes.last().unwrap());
for i in ini_i..fim_i {
for j in inicio_proibido..total {
if dados[i * total + j] >= 0.0 {
vazamentos += 1;
}
}
}
}
Ok(vazamentos)
}
/// Matriz de interpolação linear `Interp_n` (n-02 §0): R^{n_de × d} → R^{n_para × d}.
/// - downsample (n_para < n_de): média por segmento com pesos de sobreposição (área),
/// linhas somam 1 (contrato: "down = avg-pool por segmento");
/// - upsample: interpolação linear entre os 2 vizinhos.
/// Linear, determinística, sem parâmetros (gradiente flui por matmul).
pub fn matriz_interp(n_de: usize, n_para: usize, dev: &Device) -> Result<Tensor> {
let mut w = vec![0f32; n_para * n_de];
if n_de == n_para {
for i in 0..n_para {
w[i * n_de + i] = 1.0;
}
} else if n_para < n_de {
// média por segmento com bordas fracionárias
let razao = n_de as f64 / n_para as f64;
for i in 0..n_para {
let ini = i as f64 * razao;
let fim = ini + razao;
for j in 0..n_de {
let jj = j as f64;
let inter = (fim.min(jj + 1.0)) - (ini.max(jj));
if inter > 0.0 {
w[i * n_de + j] = (inter / razao) as f32;
}
}
}
} else {
// upsample linear
let razao = n_de as f64 / n_para as f64;
for i in 0..n_para {
let centro = (i as f64 + 0.5) * razao - 0.5;
let j0 = centro.floor().clamp(0.0, (n_de - 1) as f64) as usize;
let j1 = (j0 + 1).min(n_de - 1);
let fr = (centro - j0 as f64).clamp(0.0, 1.0);
w[i * n_de + j0] = (1.0 - fr) as f32;
w[i * n_de + j1] += fr as f32;
}
}
Tensor::from_vec(w, (n_para, n_de), dev)?.to_dtype(DType::F32)
}
/// Interp_m(x): redimensiona a última dimensão espacial (n_de → n_para) de
/// x com forma (..., n_de, d) via produto com a matriz (broadcast).
pub fn interp_para(x: &Tensor, n_para: usize, dev: &Device) -> Result<Tensor> {
let n_de = x.dim(D::Minus2)?;
let w = matriz_interp(n_de, n_para, dev)?;
// (n_para, n_de) x (..., n_de, d) -> (..., n_para, d)
w.broadcast_matmul(x)
}
#[cfg(test)]
mod testes {
use super::*;
#[test]
fn mascara_sem_vazamento() -> candle_core::Result<()> {
let dev = Device::Cpu;
let tamanhos = [4usize, 8, 16];
let m = mascara_block_causal(&tamanhos, 3, &dev)?;
assert_eq!(contar_vazamentos(&tamanhos, 3, &m)?, 0);
// posição do bloco 3 (último) vê tudo
let total = 3 + 4 + 8 + 16;
let dados = m.flatten_all()?.to_vec1::<f32>()?;
let ultima = (total - 1) * total;
assert!(dados[ultima..ultima + total].iter().all(|&v| v == 0.0));
Ok(())
}
#[test]
fn interp_preserva_constante() -> candle_core::Result<()> {
let dev = Device::Cpu;
// downsample de vetor constante → constante (linhas somam 1)
let x = Tensor::from_vec(vec![2.0f32; 8], (8, 1), &dev)?;
let y = interp_para(&x, 4, &dev)?;
let v = y.flatten_all()?.to_vec1::<f32>()?;
assert!(v.iter().all(|&a| (a - 2.0).abs() < 1e-5), "{v:?}");
Ok(())
}
}
|