use std::collections::BTreeMap;
use mnem_core::id::NodeId;
use mnem_core::index::hybrid::AuthoredSliceAdjacency;
use mnem_core::ppr::{PprConfig, ppr};
fn nid(i: u8) -> NodeId {
let mut bytes = [0u8; 16];
bytes[15] = i;
NodeId::from_bytes_raw(bytes)
}
#[test]
fn ppr_matches_reference_distribution() {
let n0 = nid(0);
let n1 = nid(1);
let n2 = nid(2);
let n3 = nid(3);
let n4 = nid(4);
let edges = [
(n0, n1),
(n0, n2),
(n1, n0),
(n2, n0),
(n2, n3),
(n3, n0),
(n3, n4),
(n4, n0),
];
let adj = AuthoredSliceAdjacency::new(&edges);
let mut pers: BTreeMap<NodeId, f32> = BTreeMap::new();
pers.insert(n0, 1.0);
let cfg = PprConfig {
damping: 0.85,
max_iter: 200,
eps: 1e-9,
};
let scores = ppr(&adj, &pers, cfg);
assert_eq!(scores.len(), 5);
let total: f32 = scores.values().sum();
assert!((total - 1.0).abs() < 1e-4, "L1 mass not conserved: {total}");
let s0 = scores[&n0];
let s1 = scores[&n1];
let s2 = scores[&n2];
let s3 = scores[&n3];
let s4 = scores[&n4];
assert!(s0 > s1, "seed n0 ({s0}) should outrank n1 ({s1})");
assert!(s0 > s2, "seed n0 ({s0}) should outrank n2 ({s2})");
assert!(s0 > s3);
assert!(s0 > s4);
assert!(
s1 >= s2 - 1e-3,
"n1 ({s1}) should not rank meaningfully below n2 ({s2})"
);
assert!(
s3 > s4,
"chain head n3 ({s3}) should outrank tail n4 ({s4})"
);
let tol = 1e-2f32;
let expected = [
(n0, 0.4745f32),
(n1, 0.2017),
(n2, 0.2017),
(n3, 0.0857),
(n4, 0.0364),
];
for (id, exp) in expected {
let got = scores[&id];
assert!(
(got - exp).abs() < tol,
"node {id:?}: PPR {got} deviates from reference {exp} by > {tol}"
);
}
}
#[test]
fn empty_graph_returns_empty() {
let edges: [(NodeId, NodeId); 0] = [];
let adj = AuthoredSliceAdjacency::new(&edges);
let pers: BTreeMap<NodeId, f32> = BTreeMap::new();
let scores = ppr(&adj, &pers, PprConfig::default());
assert!(scores.is_empty());
}
#[test]
fn zero_personalization_falls_back_to_uniform() {
let n0 = nid(0);
let n1 = nid(1);
let edges = [(n0, n1), (n1, n0)];
let adj = AuthoredSliceAdjacency::new(&edges);
let pers: BTreeMap<NodeId, f32> = BTreeMap::new();
let scores = ppr(&adj, &pers, PprConfig::default());
assert_eq!(scores.len(), 2);
for v in scores.values() {
assert!((v - 0.5).abs() < 1e-3, "uniform fallback broken, got {v}");
}
}
#[test]
fn deterministic_repeated_runs_byte_identical() {
let n0 = nid(0);
let n1 = nid(1);
let n2 = nid(2);
let edges = [(n0, n1), (n1, n2), (n2, n0)];
let adj = AuthoredSliceAdjacency::new(&edges);
let mut pers: BTreeMap<NodeId, f32> = BTreeMap::new();
pers.insert(n0, 1.0);
let cfg = PprConfig::default();
let a = ppr(&adj, &pers, cfg);
let b = ppr(&adj, &pers, cfg);
for (id, va) in &a {
let vb = b[id];
assert_eq!(
va.to_bits(),
vb.to_bits(),
"non-deterministic PPR output at {id:?}"
);
}
}