sim-lib-agent 0.1.6

Agent runtime surfaces for SIM.
Documentation
use sim_cookbook::fnv1a64;

pub const EMBED_DIM: usize = 256;

pub fn embed(text: &str) -> [f32; EMBED_DIM] {
    let mut vector = [0.0f32; EMBED_DIM];
    for token in text
        .split(|ch: char| !ch.is_alphanumeric())
        .filter(|token| !token.is_empty())
    {
        let index = stable_hash(&token.to_ascii_lowercase()) % EMBED_DIM;
        vector[index] += 1.0;
    }
    let norm = vector.iter().map(|value| value * value).sum::<f32>().sqrt();
    if norm > 0.0 {
        for value in &mut vector {
            *value /= norm;
        }
    }
    vector
}

pub fn cosine(a: &[f32; EMBED_DIM], b: &[f32; EMBED_DIM]) -> f32 {
    a.iter()
        .zip(b.iter())
        .map(|(left, right)| left * right)
        .sum()
}

fn stable_hash(text: &str) -> usize {
    let mut bytes = Vec::with_capacity(text.len() + 1);
    bytes.extend_from_slice(text.as_bytes());
    bytes.push(0xff);
    fnv1a64(&bytes) as usize
}

#[cfg(test)]
mod tests {
    use super::{cosine, embed};

    #[test]
    fn embedding_is_deterministic_and_normalized() {
        let first = embed("SIM vectors stay deterministic");
        let second = embed("SIM vectors stay deterministic");
        assert_eq!(first, second);
        let norm = cosine(&first, &first);
        assert!((norm - 1.0).abs() < 0.0001);
    }

    #[test]
    fn embedding_of_empty_text_is_zero_vector() {
        let empty = embed("   ");
        assert_eq!(cosine(&empty, &empty), 0.0);
    }
}