use super::*;
use crate::candle::encoder::scatter_pool::{
attention_scores_from_vector, pool_by_scatter, query_over_features,
};
use crate::candle::value_transform::anscombe_lite;
use candle_core::{DType, Device, Tensor};
use candle_nn::ops;
const D: usize = 9;
const H: usize = 4;
const N: usize = 3;
const K: usize = 3;
fn ramp(n: usize, a: f32, b: f32) -> Vec<f32> {
(0..n)
.map(|i| a + b * ((i as f32 * 0.7).sin() + 0.3 * (i as f32 * 0.11).cos()))
.collect()
}
fn has_visible(visible: &Tensor) -> Tensor {
visible
.sum_keepdim(1)
.unwrap()
.gt(0.0)
.unwrap()
.to_dtype(DType::F32)
.unwrap()
}
struct Fixture {
gate_nd: Tensor,
idx_nk: Tensor,
query: Tensor,
features: std::sync::Arc<FeatureEmbedding>,
dev: Device,
}
fn fixture() -> Fixture {
let dev = Device::Cpu;
let values = Tensor::from_vec(ramp(N * D, 5.0, 4.0), (N, D), &dev).unwrap();
let gate_nd = anscombe_lite(&values, None, None).unwrap();
let ids: Vec<u32> = vec![
1, 3, 5, 0, 2, 6, 4, 7, 8, ];
let idx_nk = Tensor::from_vec(ids, (N, K), &dev).unwrap();
let query = Tensor::from_vec(ramp(H, 0.1, 0.9), (1, H), &dev).unwrap();
let table = Tensor::from_vec(ramp(D * H, 0.0, 1.0), (D, H), &dev).unwrap();
Fixture {
gate_nd,
idx_nk,
query,
features: FeatureEmbedding::fixed(table),
dev,
}
}
fn visible_on_support(f: &Fixture) -> Tensor {
let ids: Vec<Vec<u32>> = f.idx_nk.to_vec2().unwrap();
let mut buf = vec![0f32; N * D];
for (r, row) in ids.iter().enumerate() {
for &g in row {
buf[r * D + g as usize] = 1.0;
}
}
Tensor::from_vec(buf, (N, D), &f.dev).unwrap()
}
fn dense_path(f: &Fixture, visible_nd: &Tensor) -> (Tensor, Tensor) {
let rq_d = query_over_features(&f.features, &f.query).unwrap();
let scores =
attention_scores_dense(&f.gate_nd, &rq_d, Some(visible_nd), 1.0 / (H as f64).sqrt())
.unwrap();
let attn = ops::softmax(&scores, 1).unwrap();
let pooled = pool_dense(&attn, &f.gate_nd, &f.features).unwrap();
let pooled = pooled.broadcast_mul(&has_visible(visible_nd)).unwrap();
(attn, pooled)
}
fn scatter_path(f: &Fixture) -> Tensor {
let gate_nk = f
.gate_nd
.gather(&f.idx_nk.to_dtype(DType::U32).unwrap(), 1)
.unwrap();
let visible_nk = Tensor::ones((N, K), DType::F32, &f.dev).unwrap();
let rq_d = query_over_features(&f.features, &f.query).unwrap();
let scores = attention_scores_from_vector(
&gate_nk,
&f.idx_nk,
&rq_d,
&visible_nk,
1.0 / (H as f64).sqrt(),
)
.unwrap();
let attn = ops::softmax(&scores, 1).unwrap();
pool_by_scatter(&attn, &gate_nk, &f.idx_nk, &f.features).unwrap()
}
fn assert_close(got: &Tensor, want: &Tensor, tol: f32, what: &str) {
let g: Vec<Vec<f32>> = got.to_vec2().unwrap();
let w: Vec<Vec<f32>> = want.to_vec2().unwrap();
assert_eq!(g.len(), w.len(), "{what}: row count");
for (i, (gr, wr)) in g.iter().zip(&w).enumerate() {
assert_eq!(gr.len(), wr.len(), "{what}: row {i} width");
for (j, (a, b)) in gr.iter().zip(wr).enumerate() {
assert!(
(a - b).abs() <= tol,
"{what}: [{i},{j}] {a} vs {b} (tol {tol})"
);
}
}
}
#[test]
fn dense_pool_equals_the_scatter_pool_on_a_planted_support() {
let f = fixture();
let visible_nd = visible_on_support(&f);
let (_, dense) = dense_path(&f, &visible_nd);
let scatter = scatter_path(&f);
assert_close(&dense, &scatter, 1e-5, "dense vs scatter pool");
}
#[test]
fn a_hidden_gene_has_zero_attention_and_zero_weight() {
let f = fixture();
let visible_nd = visible_on_support(&f);
let (attn, _) = dense_path(&f, &visible_nd);
let a: Vec<Vec<f32>> = attn.to_vec2().unwrap();
let v: Vec<Vec<f32>> = visible_nd.to_vec2().unwrap();
for (r, (arow, vrow)) in a.iter().zip(&v).enumerate() {
let mut seen = 0f32;
for (g, (&w, &vis)) in arow.iter().zip(vrow).enumerate() {
if vis == 0.0 {
assert_eq!(w, 0.0, "row {r} gene {g} is hidden but carries weight {w}");
} else {
seen += w;
}
}
assert!(
(seen - 1.0).abs() < 1e-5,
"row {r}: the visible genes must carry all the mass, got {seen}"
);
}
let w_nd = (&attn * &f.gate_nd).unwrap().to_vec2::<f32>().unwrap();
for (r, (wrow, vrow)) in w_nd.iter().zip(&v).enumerate() {
for (g, (&w, &vis)) in wrow.iter().zip(vrow).enumerate() {
if vis == 0.0 {
assert_eq!(w, 0.0, "row {r} gene {g}: hidden weight {w}");
}
}
}
}
#[test]
fn a_fully_hidden_row_pools_to_zero() {
let f = fixture();
let mut buf: Vec<f32> = visible_on_support(&f)
.flatten_all()
.unwrap()
.to_vec1()
.unwrap();
for slot in buf.iter_mut().take(D) {
*slot = 0.0; }
let visible_nd = Tensor::from_vec(buf, (N, D), &f.dev).unwrap();
let (_, pooled) = dense_path(&f, &visible_nd);
let p: Vec<Vec<f32>> = pooled.to_vec2().unwrap();
for (h, &x) in p[0].iter().enumerate() {
assert_eq!(x, 0.0, "fully hidden row: pool[0,{h}] = {x}");
}
assert!(
p[1].iter().any(|x| x.abs() > 1e-6),
"a row that can still see must not be zeroed"
);
}