holographic-memory 0.6.1

A high-performance Holographic Memory System (HMS) implementing Vector Symbolic Architectures (VSA).
Documentation
// Copyright 2024-2026 WritersLogic Contributors
// SPDX-License-Identifier: AGPL-3.0-or-later

use holographic_memory::core::block_codes::{BlockCodeBundle, BlockCodeConfig, BlockCodeVec};
use holographic_memory::core::entangled::EntangledHVec;

fn measure_bloom(dim: usize, density_denom: usize, max_items: usize, n_probes: usize) {
    println!("# BLOOM dim={} denom={}", dim, density_denom);
    println!("scheme\tn_items\tmember_mean\tmember_min\tnonmember_mean\tnonmember_max\tgap");

    let items: Vec<EntangledHVec> = (0..max_items)
        .map(|i| EntangledHVec::new_with_density(dim, density_denom, i as u64 * 37 + 1))
        .collect();
    let non_members: Vec<EntangledHVec> = (0..n_probes)
        .map(|i| {
            EntangledHVec::new_with_density(dim, density_denom, (max_items + i) as u64 * 37 + 9999)
        })
        .collect();

    for &n_items in &load_points(max_items) {
        let bundle = EntangledHVec::bundle_bloom(&items[..n_items]);
        let density = bundle.indices().len() as f64 / dim as f64;

        let member_sims: Vec<f64> = items[..n_items]
            .iter()
            .map(|item| item.corrected_containment(&bundle))
            .collect();
        let member_mean = mean(&member_sims);
        let member_min = fmin(&member_sims);

        let nonmember_sims: Vec<f64> = non_members
            .iter()
            .map(|item| item.corrected_containment(&bundle))
            .collect();
        let nonmember_mean = mean(&nonmember_sims);
        let nonmember_max = fmax(&nonmember_sims);

        println!(
            "bloom\t{}\t{:.6}\t{:.6}\t{:.6}\t{:.6}\t{:.6}",
            n_items,
            member_mean,
            member_min,
            nonmember_mean,
            nonmember_max,
            member_min - nonmember_max
        );

        if density > 0.995 {
            break;
        }
    }
    println!();
}

fn measure_sbc(n_blocks: usize, block_len: usize, max_items: usize, n_probes: usize) {
    let cfg = BlockCodeConfig::new(n_blocks, block_len);
    println!(
        "# SBC dim={} blocks={} block_len={}",
        cfg.dim(),
        n_blocks,
        block_len
    );
    println!("scheme\tn_items\tmember_mean\tmember_min\tnonmember_mean\tnonmember_max\tgap");

    let items: Vec<BlockCodeVec> = (0..max_items)
        .map(|i| BlockCodeVec::new_deterministic(&cfg, i as u64 * 37 + 1))
        .collect();
    let non_members: Vec<BlockCodeVec> = (0..n_probes)
        .map(|i| BlockCodeVec::new_deterministic(&cfg, (max_items + i) as u64 * 37 + 9999))
        .collect();

    let mut bundle = BlockCodeBundle::new(&cfg);

    for &n_items in &load_points(max_items) {
        while bundle.n_items() < n_items {
            bundle.add(&items[bundle.n_items()]);
        }

        let member_scores: Vec<f64> = items[..n_items]
            .iter()
            .map(|item| bundle.count_score(item))
            .collect();
        let nonmember_scores: Vec<f64> = non_members
            .iter()
            .map(|item| bundle.count_score(item))
            .collect();

        let expected_nonmember = n_items as f64 / block_len as f64;
        let member_mean = mean(&member_scores) - expected_nonmember;
        let member_min = fmin(&member_scores) - expected_nonmember;
        let nonmember_mean = mean(&nonmember_scores) - expected_nonmember;
        let nonmember_max = fmax(&nonmember_scores) - expected_nonmember;

        println!(
            "sbc\t{}\t{:.6}\t{:.6}\t{:.6}\t{:.6}\t{:.6}",
            n_items,
            member_mean,
            member_min,
            nonmember_mean,
            nonmember_max,
            member_min - nonmember_max
        );
    }
    println!();
}

fn load_points(max_items: usize) -> Vec<usize> {
    let mut points = Vec::new();
    let mut n = 1;
    while n <= max_items {
        points.push(n);
        if n < 10 {
            n += 1;
        } else if n < 100 {
            n += 10;
        } else if n < 1000 {
            n += 100;
        } else if n < 10000 {
            n += 1000;
        } else {
            n += 5000;
        }
    }
    if *points.last().unwrap_or(&0) != max_items {
        points.push(max_items);
    }
    points
}

fn mean(vals: &[f64]) -> f64 {
    vals.iter().sum::<f64>() / vals.len() as f64
}
fn fmin(vals: &[f64]) -> f64 {
    vals.iter().cloned().fold(f64::INFINITY, f64::min)
}
fn fmax(vals: &[f64]) -> f64 {
    vals.iter().cloned().fold(f64::NEG_INFINITY, f64::max)
}

fn main() {
    println!("# Similarity decay: Bloom vs SBC at D=16384");
    println!("# Bloom: corrected containment (member=1.0 until cliff)");
    println!("# SBC: count_score - expected_noise (member≈1.0, nonmember≈0)");
    println!("# gap = member_min - nonmember_max (>0 means perfect separation)");
    println!();

    measure_bloom(16384, 256, 5000, 200);

    measure_sbc(256, 64, 5000, 200);
    measure_sbc(64, 256, 5000, 200);
    measure_sbc(16, 1024, 10000, 200);
}