sim-lib-agent 0.1.2

Agent runtime surfaces for SIM.
Documentation
use std::hash::{Hash, Hasher};

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 hasher = StableHasher(0xcbf29ce484222325);
    text.hash(&mut hasher);
    hasher.0 as usize
}

struct StableHasher(u64);

impl Hasher for StableHasher {
    fn finish(&self) -> u64 {
        self.0
    }

    fn write(&mut self, bytes: &[u8]) {
        for byte in bytes {
            self.0 ^= u64::from(*byte);
            self.0 = self.0.wrapping_mul(0x100000001b3);
        }
    }
}

#[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);
    }
}