use holographic_memory::EntangledHVec;
const D: usize = 16_384;
const DENSITY_DENOM: usize = 256;
const N_ENTITIES: usize = 1024;
const N_RELATIONS: usize = 8;
const HOT_PATHS: usize = 24;
const WARMUP_ROUNDS: usize = 40;
const W_STORE: i64 = 1;
const W_STRENGTHEN: i64 = 2;
const DECAY_NUM: i64 = 7;
const DECAY_DEN: i64 = 8;
const SEEDS: u64 = 12;
const LOADS: &[usize] = &[64, 128, 256, 512, 1024, 2048];
fn mix(x: u64, k: u64) -> u64 {
let mut h = x.wrapping_mul(0x9E37_79B9_7F4A_7C15) ^ k.wrapping_mul(0xC2B2_AE3D_27D4_EB4F);
h ^= h >> 29;
h = h.wrapping_mul(0xBF58_476D_1CE4_E5B9);
h ^= h >> 32;
h
}
fn subj_mask(rel: usize) -> u32 {
(mix(rel as u64, 0x5B_1EC7) % D as u64) as u32 | 1
}
fn obj_mask(rel: usize) -> u32 {
(mix(rel as u64, 0x0B_1EC7) % D as u64) as u32 | 1
}
fn permd(hv: &EntangledHVec, mask: u32) -> Vec<u32> {
hv.indices().iter().map(|&i| i ^ mask).collect()
}
fn edge_indices(cb: &[EntangledHVec], s: usize, rel: usize, o: usize) -> Vec<u32> {
let mut v = permd(&cb[s], subj_mask(rel));
v.extend(permd(&cb[o], obj_mask(rel)));
v.sort_unstable();
v.dedup();
v
}
fn score(field: &[i64], indices: &[u32], gmean: f64) -> f64 {
if indices.is_empty() {
return 0.0;
}
let s: i64 = indices.iter().map(|&i| field[i as usize]).sum();
(s as f64 / indices.len() as f64) - gmean
}
fn gmean(field: &[i64]) -> f64 {
field.iter().sum::<i64>() as f64 / field.len() as f64
}
fn auc(correct: &[f64], wrong: &[f64]) -> f64 {
let mut wins = 0.0;
for &c in correct {
for &w in wrong {
if c > w {
wins += 1.0;
} else if (c - w).abs() < 1e-9 {
wins += 0.5;
}
}
}
wins / (correct.len() * wrong.len()).max(1) as f64
}
fn mean_sd(v: &[f64]) -> (f64, f64) {
let m = v.iter().sum::<f64>() / v.len() as f64;
let sd = (v.iter().map(|x| (x - m).powi(2)).sum::<f64>() / v.len() as f64).sqrt();
(m, sd)
}
struct Edge {
s: usize,
rel: usize,
o: usize,
idx: Vec<u32>,
}
struct Path {
e1: usize,
e2: usize,
}
struct Graph {
cb: Vec<EntangledHVec>,
edges: Vec<Edge>,
paths: Vec<Path>,
}
fn build_graph(n_edges: usize, seed: u64) -> Graph {
let cb: Vec<EntangledHVec> = (0..N_ENTITIES)
.map(|i| EntangledHVec::new_with_density(D, DENSITY_DENOM, mix(i as u64, seed)))
.collect();
let mut edges = Vec::with_capacity(n_edges);
for i in 0..n_edges {
let s = (mix(i as u64, seed ^ 0xE0) % N_ENTITIES as u64) as usize;
let rel = (mix(i as u64, seed ^ 0xE1) % N_RELATIONS as u64) as usize;
let o = (mix(i as u64, seed ^ 0xE2) % N_ENTITIES as u64) as usize;
let idx = edge_indices(&cb, s, rel, o);
edges.push(Edge { s, rel, o, idx });
}
let mut paths = Vec::new();
for i in 0..n_edges {
if let Some(j) = (0..n_edges).find(|&j| j != i && edges[j].s == edges[i].o) {
paths.push(Path { e1: i, e2: j });
}
if paths.len() >= HOT_PATHS * 4 {
break;
}
}
Graph { cb, edges, paths }
}
fn fold(field: &mut [i64], idx: &[u32], w: i64) {
for &i in idx {
field[i as usize] += w;
}
}
fn decay(field: &mut [i64]) {
for v in field.iter_mut() {
*v = *v * DECAY_NUM / DECAY_DEN;
}
}
fn mispath_second_edge(g: &Graph, p: &Path, seed: u64) -> Vec<u32> {
let e2 = &g.edges[p.e2];
let wrong_s = (mix(p.e2 as u64, seed ^ 0x9999) % N_ENTITIES as u64) as usize;
let wrong_s = if wrong_s == e2.s {
(wrong_s + 1) % N_ENTITIES
} else {
wrong_s
};
edge_indices(&g.cb, wrong_s, e2.rel, e2.o)
}
struct Out {
static_path_auc: f64,
plastic_path_auc: f64,
static_gen_auc: f64,
plastic_gen_auc: f64,
}
fn run(n_edges: usize, seed: u64) -> Out {
let g = build_graph(n_edges, seed);
let n_hot = HOT_PATHS.min(g.paths.len());
if n_hot == 0 {
return Out {
static_path_auc: 0.5,
plastic_path_auc: 0.5,
static_gen_auc: 0.5,
plastic_gen_auc: 0.5,
};
}
let mut sf = vec![0i64; D];
for e in &g.edges {
fold(&mut sf, &e.idx, W_STORE);
}
let mut pf = vec![0i64; D];
for e in &g.edges {
fold(&mut pf, &e.idx, W_STORE);
}
for _ in 0..WARMUP_ROUNDS {
for p in g.paths.iter().take(n_hot) {
fold(&mut pf, &g.edges[p.e1].idx, W_STRENGTHEN);
fold(&mut pf, &g.edges[p.e2].idx, W_STRENGTHEN);
}
decay(&mut pf);
}
let (sm, pm) = (gmean(&sf), gmean(&pf));
let path_score = |field: &[i64], gm: f64, p: &Path| -> f64 {
score(field, &g.edges[p.e1].idx, gm).min(score(field, &g.edges[p.e2].idx, gm))
};
let mut s_valid = Vec::new();
let mut s_mis = Vec::new();
let mut p_valid = Vec::new();
let mut p_mis = Vec::new();
for p in g.paths.iter().take(n_hot) {
let mis2 = mispath_second_edge(&g, p, seed);
s_valid.push(path_score(&sf, sm, p));
s_mis.push(score(&sf, &g.edges[p.e1].idx, sm).min(score(&sf, &mis2, sm)));
p_valid.push(path_score(&pf, pm, p));
p_mis.push(score(&pf, &g.edges[p.e1].idx, pm).min(score(&pf, &mis2, pm)));
}
let mut s_share = Vec::new();
let mut s_unrel = Vec::new();
let mut p_share = Vec::new();
let mut p_unrel = Vec::new();
for p in g.paths.iter().take(n_hot) {
let hot = &g.edges[p.e1];
let novel_o = (mix(p.e1 as u64, seed ^ 0x7777) % N_ENTITIES as u64) as usize;
let shared = edge_indices(&g.cb, hot.s, hot.rel, novel_o); let u_s = (mix(p.e1 as u64, seed ^ 0x8888) % N_ENTITIES as u64) as usize;
let u_r = (mix(p.e1 as u64, seed ^ 0x8889) % N_RELATIONS as u64) as usize;
let u_o = (mix(p.e1 as u64, seed ^ 0x888A) % N_ENTITIES as u64) as usize;
let unrelated = edge_indices(&g.cb, u_s, u_r, u_o); s_share.push(score(&sf, &shared, sm));
s_unrel.push(score(&sf, &unrelated, sm));
p_share.push(score(&pf, &shared, pm));
p_unrel.push(score(&pf, &unrelated, pm));
}
Out {
static_path_auc: auc(&s_valid, &s_mis),
plastic_path_auc: auc(&p_valid, &p_mis),
static_gen_auc: auc(&s_share, &s_unrel),
plastic_gen_auc: auc(&p_share, &p_unrel),
}
}
fn main() {
println!("path-plasticity | D={D} entities={N_ENTITIES} rels={N_RELATIONS} hot_paths={HOT_PATHS} seeds={SEEDS}");
println!("PATH AUC: valid 2-hop path vs broken-connection mis-path (chance 0.5).");
println!("GEN AUC (anti-cache): untraversed edge sharing (subj,rel) with a hot edge vs a");
println!("fully-unrelated novel edge. If plastic GEN >> 0.5 (and >> static), strengthening");
println!("generalized through shared substructure -- a cache cannot do this.\n");
println!(
"{:>6} {:>15} {:>15} {:>15} {:>15}",
"edges", "static PATH", "plastic PATH", "static GEN", "plastic GEN"
);
for &n in LOADS {
let mut sp = Vec::new();
let mut pp = Vec::new();
let mut sg = Vec::new();
let mut pg = Vec::new();
for seed in 0..SEEDS {
let o = run(n, seed);
sp.push(o.static_path_auc);
pp.push(o.plastic_path_auc);
sg.push(o.static_gen_auc);
pg.push(o.plastic_gen_auc);
}
println!(
"{n:>6} {:>9.3} ({:>4.2}) {:>9.3} ({:>4.2}) {:>9.3} ({:>4.2}) {:>9.3} ({:>4.2})",
mean_sd(&sp).0,
mean_sd(&sp).1,
mean_sd(&pp).0,
mean_sd(&pp).1,
mean_sd(&sg).0,
mean_sd(&sg).1,
mean_sd(&pg).0,
mean_sd(&pg).1,
);
}
println!("\nKILL (cache verdict): if plastic GEN ~ 0.5 or ~ static GEN, strengthening did");
println!(
"NOT generalize through shared substructure -> it is a cache, holographic thesis dead."
);
}