use ruvector_core::embeddings::{EmbeddingProvider, LatticeEmbedding};
fn cosine(a: &[f32], b: &[f32]) -> f32 {
let dot: f32 = a.iter().zip(b).map(|(x, y)| x * y).sum();
let na = a.iter().map(|x| x * x).sum::<f32>().sqrt();
let nb = b.iter().map(|x| x * x).sum::<f32>().sqrt();
if na == 0.0 || nb == 0.0 {
0.0
} else {
dot / (na * nb)
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("=== LatticeEmbedding (native pure-Rust) Example ===\n");
let provider = LatticeEmbedding::from_pretrained("bge-small-en-v1.5")?;
println!(
"✓ Loaded provider: {} ({} dimensions)\n",
provider.name(),
provider.dimensions()
);
let passage = "The Eiffel Tower is located in Paris, France.";
let query = "Where is the Eiffel Tower?";
println!("Passage: {passage:?}");
println!("Query: {query:?}\n");
let passage_vec = provider.embed(passage)?;
let query_as_passage = provider.embed(query)?;
let query_as_query = provider.embed_query(query)?;
println!("--- Asymmetry: query embedded as passage vs. as query ---");
println!(
"cosine(passage, query-as-passage) = {:.4}",
cosine(&passage_vec, &query_as_passage)
);
println!(
"cosine(passage, query-as-query) = {:.4} <- embed_query()",
cosine(&passage_vec, &query_as_query)
);
let self_sim = cosine(&query_as_passage, &query_as_query);
println!("\ncosine(query-as-passage, query-as-query) = {self_sim:.4}");
println!(
"A value below 1.0 confirms embed_query() prepended BGE's retrieval\n\
instruction and produced a different vector. That is the whole point:\n\
queries and passages live in the same space but are encoded by\n\
different protocols. (A single pair's absolute cosine is not the\n\
retrieval signal; what matters is ranking across a corpus, where\n\
embedding queries with embed_query() is what BGE was trained for.)"
);
Ok(())
}