qql-embed 0.2.1

Shared dense and sparse embedding resolution for QQL runtimes
Documentation
use crate::sparse;

#[test]
fn test_tokenize_lowercases_and_filters() {
    let got = sparse::tokenize("Hello, World! 123 TEST_token");
    assert_eq!(got, vec!["hello", "world", "123", "test_token"]);
}

#[test]
fn test_tokenize_filters_single_chars_except_special() {
    let got = sparse::tokenize("a b c d go rs");
    assert_eq!(got, vec!["c", "go", "rs"]);
}

#[test]
fn test_tokenize_handles_hyphenated_medical_terms() {
    let got = sparse::tokenize("B-cell anti-NMDA CD19-negative");
    assert_eq!(got.len(), 5);
    for want in &["cell", "anti", "nmda", "cd19", "negative"] {
        assert!(got.contains(&want.to_string()), "missing token: {want}");
    }
}

#[test]
fn test_tokenize_handles_unicode() {
    let got = sparse::tokenize("Привет мир hello-world");
    assert_eq!(got.len(), 4);
    for want in &["привет", "мир", "hello", "world"] {
        assert!(got.contains(&want.to_string()), "missing token: {want}");
    }
}

#[test]
fn test_tokenize_handles_underscore() {
    let got = sparse::tokenize("test_fn main_loop");
    assert_eq!(got, vec!["test_fn", "main_loop"]);
}

#[test]
fn test_hash_token_deterministic() {
    let a = sparse::hash_token("hello");
    let b = sparse::hash_token("hello");
    assert_eq!(a, b);
}

#[test]
fn test_hash_token_different_for_different_inputs() {
    let a = sparse::hash_token("hello");
    let b = sparse::hash_token("world");
    assert_ne!(a, b);
}

#[test]
fn test_hash_token_length_prefix_avoids_collision() {
    let a = sparse::hash_token("ab");
    let b = sparse::hash_token("abc");
    assert_ne!(a, b);
}

#[test]
fn test_build_query_uses_log_tf() {
    let v = sparse::build_query("hello hello world");
    assert_eq!(v.indices.len(), 2);
    assert_eq!(v.values.len(), 2);

    let hello_idx = sparse::hash_token("hello");
    let world_idx = sparse::hash_token("world");

    let mut hello_value = 0.0f32;
    let mut world_value = 0.0f32;
    for i in 0..v.indices.len() {
        if v.indices[i] == hello_idx {
            hello_value = v.values[i];
        }
        if v.indices[i] == world_idx {
            world_value = v.values[i];
        }
    }

    let expected_hello = 1.0 + (2.0f64).ln() as f32;
    assert!(
        (hello_value - expected_hello).abs() < 0.0001,
        "hello value mismatch: {hello_value} != {expected_hello}"
    );
    assert!(
        (world_value - 1.0).abs() < 0.0001,
        "world value mismatch: {world_value} != 1.0"
    );
}

#[test]
fn test_build_document_uses_bm25_saturated_tf() {
    let v = sparse::build_document("hello hello world", 1.2, 0.75, 256.0);
    assert_eq!(v.indices.len(), 2);

    let hello_idx = sparse::hash_token("hello");
    let world_idx = sparse::hash_token("world");

    let mut hello_value = 0.0f32;
    let mut world_value = 0.0f32;
    for i in 0..v.indices.len() {
        if v.indices[i] == hello_idx {
            hello_value = v.values[i];
        }
        if v.indices[i] == world_idx {
            world_value = v.values[i];
        }
    }

    let denom = 1.2 * (0.25 + 0.75 * 3.0 / 256.0);
    let expected_hello = (2.0 * 2.2) / (2.0 + denom);
    let expected_world = (1.0 * 2.2) / (1.0 + denom);

    assert!(
        (hello_value - expected_hello as f32).abs() < 0.0001,
        "hello value mismatch: {hello_value} != {expected_hello}"
    );
    assert!(
        (world_value - expected_world as f32).abs() < 0.0001,
        "world value mismatch: {world_value} != {expected_world}"
    );
}

#[test]
fn test_build_returns_empty_for_empty_text() {
    let doc = sparse::build_document_default("");
    assert!(
        doc.indices.is_empty(),
        "expected empty indices for empty document"
    );
    assert!(
        doc.values.is_empty(),
        "expected empty values for empty document"
    );

    let q = sparse::build_query("");
    assert!(
        q.indices.is_empty(),
        "expected empty indices for empty query"
    );
    assert!(q.values.is_empty(), "expected empty values for empty query");
}