use std::fs;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use std::time::SystemTime;
use crate::parallel::*;
use crate::embeddings::{cosine_similarity_simd, EmbeddingProvider};
#[allow(dead_code)]
pub struct EmbeddingIndex {
data: memmap2::Mmap,
embedded_count: usize,
dim: usize,
max_depth: u32,
}
type CachedEmbeddingIndex = (PathBuf, u64, Option<SystemTime>, Arc<EmbeddingIndex>);
static EMBED_INDEX_CACHE: Mutex<Option<CachedEmbeddingIndex>> = Mutex::new(None);
pub fn open_embedding_cached(path: &Path) -> Option<Arc<EmbeddingIndex>> {
let meta = fs::metadata(path).ok()?;
let key = (meta.len(), meta.modified().ok());
let mut guard = EMBED_INDEX_CACHE.lock().ok()?;
let hit = match guard.as_ref() {
Some((cached_path, len, mtime, cached))
if cached_path == path && *len == key.0 && *mtime == key.1 =>
{
Some(Arc::clone(cached))
}
_ => None,
};
if let Some(hit) = hit {
return Some(hit);
}
let parsed = Arc::new(EmbeddingIndex::open(path)?);
let handle = Arc::clone(&parsed);
*guard = Some((path.to_path_buf(), key.0, key.1, parsed));
Some(handle)
}
const HEADER_SIZE: usize = 12;
impl EmbeddingIndex {
pub fn open(path: &Path) -> Option<Self> {
if !path.exists() {
return None;
}
let file = fs::File::open(path).ok()?;
let data = unsafe { memmap2::Mmap::map(&file).ok()? };
if data.len() < HEADER_SIZE {
return None;
}
let embedded_count = u32::from_le_bytes(data[0..4].try_into().unwrap()) as usize;
let dim = u32::from_le_bytes(data[4..8].try_into().unwrap()) as usize;
let max_depth = u32::from_le_bytes(data[8..12].try_into().unwrap());
let expected = HEADER_SIZE
+ embedded_count
.checked_mul(4)
.and_then(|v| v.checked_add(embedded_count.checked_mul(dim * 4)?))
.unwrap_or(usize::MAX);
if expected > data.len() {
return None;
}
Some(EmbeddingIndex {
data,
embedded_count,
dim,
max_depth,
})
}
pub fn block_count(&self) -> usize {
self.embedded_count
}
pub fn embedding(&self, block_idx: usize) -> Option<&[f32]> {
let ids = self.block_ids();
let pos = ids.binary_search(&(block_idx as u32)).ok()?;
let offset = HEADER_SIZE + self.embedded_count * 4 + pos * self.dim * 4;
let ptr = self.data[offset..].as_ptr() as *const f32;
Some(unsafe { std::slice::from_raw_parts(ptr, self.dim) })
}
pub fn dim(&self) -> usize {
self.dim
}
#[allow(dead_code)]
pub fn max_depth(&self) -> u8 {
self.max_depth as u8
}
pub fn search(&self, query_emb: &[f32], k: usize) -> Vec<(f32, usize)> {
search_with_floor(
query_emb,
k,
self.dim,
&self.data,
HEADER_SIZE,
self.embedded_count,
self.block_ids(),
)
}
pub fn all_block_ids(&self) -> &[u32] {
self.block_ids()
}
pub fn similarity_of(&self, block_idx: usize, query_emb: &[f32]) -> Option<f32> {
if query_emb.len() != self.dim {
return None;
}
let ids = self.block_ids();
let pos = ids.binary_search(&(block_idx as u32)).ok()?;
let offset = HEADER_SIZE + self.embedded_count * 4 + pos * self.dim * 4;
let ptr = self.data[offset..].as_ptr() as *const f32;
let emb = unsafe { std::slice::from_raw_parts(ptr, self.dim) };
Some(cosine_similarity_simd(query_emb, emb))
}
pub fn full_rank_of(&self, block_idx: usize, query_emb: &[f32]) -> Option<(usize, f32)> {
if query_emb.len() != self.dim {
return None;
}
let ids = self.block_ids();
let target = ids.binary_search(&(block_idx as u32)).ok()?;
let target_off = HEADER_SIZE + self.embedded_count * 4 + target * self.dim * 4;
let tptr = self.data[target_off..].as_ptr() as *const f32;
let target_emb = unsafe { std::slice::from_raw_parts(tptr, self.dim) };
let target_sim = cosine_similarity_simd(query_emb, target_emb);
let mut higher = 0usize;
for i in 0..self.embedded_count {
let offset = HEADER_SIZE + self.embedded_count * 4 + i * self.dim * 4;
let ptr = self.data[offset..].as_ptr() as *const f32;
let emb = unsafe { std::slice::from_raw_parts(ptr, self.dim) };
if cosine_similarity_simd(query_emb, emb) > target_sim {
higher += 1;
}
}
Some((higher, target_sim))
}
fn block_ids(&self) -> &[u32] {
let start = HEADER_SIZE;
let end = start + self.embedded_count * 4;
let ptr = self.data[start..end].as_ptr() as *const u32;
unsafe { std::slice::from_raw_parts(ptr, self.embedded_count) }
}
}
pub fn min_embed_chars() -> usize {
std::env::var("MICROSCOPE_MIN_EMBED_CHARS")
.ok()
.and_then(|v| v.parse::<usize>().ok())
.unwrap_or(20)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EmbedVerdict {
Keep,
Short,
Unencodable,
Mojibake,
}
pub fn quality_gate(text: &str, min_chars: usize) -> EmbedVerdict {
if text == "<bin>" || text == "[out of bounds]" {
return EmbedVerdict::Unencodable;
}
let total = text.chars().count();
if total < min_chars {
return EmbedVerdict::Short;
}
let suspicious = text
.chars()
.filter(|c| ('\u{80}'..='\u{2ff}').contains(c))
.count();
if total > 0 && suspicious * 4 > total {
return EmbedVerdict::Mojibake;
}
EmbedVerdict::Keep
}
pub const APPEND_EMBEDDINGS_FILE: &str = "append_embeddings.bin";
const APPEND_EMBEDDINGS_MAGIC: &[u8; 4] = b"AEM1";
const APPEND_EMBEDDINGS_HEADER: usize = 12;
pub struct AppendEmbeddings {
pub dim: usize,
pub entries: Vec<(u32, Vec<f32>)>,
}
impl AppendEmbeddings {
pub fn new(dim: usize) -> Self {
AppendEmbeddings {
dim,
entries: Vec::new(),
}
}
pub fn open(path: &Path, dim: usize) -> Option<Self> {
let data = fs::read(path).ok()?;
if data.len() < APPEND_EMBEDDINGS_HEADER || &data[0..4] != APPEND_EMBEDDINGS_MAGIC {
return None;
}
let file_dim = u32::from_le_bytes(data[4..8].try_into().ok()?) as usize;
if dim == 0 || file_dim != dim {
return None;
}
let count = u32::from_le_bytes(data[8..12].try_into().ok()?) as usize;
let stride = 4 + dim * 4;
let mut entries = Vec::with_capacity(count.min(65_536));
for i in 0..count {
let off = APPEND_EMBEDDINGS_HEADER + i * stride;
if off + stride > data.len() {
break;
}
let index = u32::from_le_bytes(data[off..off + 4].try_into().ok()?);
let mut v = Vec::with_capacity(dim);
for j in 0..dim {
let p = off + 4 + j * 4;
v.push(f32::from_le_bytes(data[p..p + 4].try_into().ok()?));
}
entries.push((index, v));
}
Some(AppendEmbeddings { dim, entries })
}
pub fn push(&mut self, append_index: u32, vector: Vec<f32>) {
self.entries.push((append_index, vector));
}
}
type CachedAppendIndex = (
PathBuf,
u64,
Option<SystemTime>,
usize,
Arc<AppendEmbeddings>,
);
static APPEND_CACHE: Mutex<Option<CachedAppendIndex>> = Mutex::new(None);
pub fn open_append_cached(path: &Path, dim: usize) -> Option<Arc<AppendEmbeddings>> {
let meta = fs::metadata(path).ok()?;
let key = (meta.len(), meta.modified().ok());
let mut guard = APPEND_CACHE.lock().ok()?;
let hit = match guard.as_ref() {
Some((cached_path, len, mtime, cached_dim, cached))
if cached_path == path && *len == key.0 && *mtime == key.1 && *cached_dim == dim =>
{
Some(Arc::clone(cached))
}
_ => None,
};
if let Some(hit) = hit {
return Some(hit);
}
let parsed = Arc::new(AppendEmbeddings::open(path, dim)?);
let handle = Arc::clone(&parsed);
*guard = Some((path.to_path_buf(), key.0, key.1, dim, parsed));
Some(handle)
}
impl AppendEmbeddings {
pub fn save(&self, path: &Path) -> Result<(), String> {
let stride = 4 + self.dim * 4;
let mut buf = Vec::with_capacity(APPEND_EMBEDDINGS_HEADER + self.entries.len() * stride);
buf.extend_from_slice(APPEND_EMBEDDINGS_MAGIC);
buf.extend_from_slice(&(self.dim as u32).to_le_bytes());
buf.extend_from_slice(&(self.entries.len() as u32).to_le_bytes());
for (index, v) in &self.entries {
if v.len() != self.dim {
return Err(format!(
"append embedding width {} does not match header dim {}",
v.len(),
self.dim
));
}
buf.extend_from_slice(&index.to_le_bytes());
for f in v {
buf.extend_from_slice(&f.to_le_bytes());
}
}
let tmp = path.with_extension("bin.tmp");
fs::write(&tmp, &buf).map_err(|e| format!("write {}: {}", APPEND_EMBEDDINGS_FILE, e))?;
fs::rename(&tmp, path).map_err(|e| format!("rename {}: {}", APPEND_EMBEDDINGS_FILE, e))?;
Ok(())
}
pub fn search(&self, query_emb: &[f32], k: usize) -> Vec<(f32, u32)> {
if query_emb.len() != self.dim {
return vec![];
}
let mut out: Vec<(f32, u32)> = self
.entries
.iter()
.filter_map(|(i, v)| {
let sim = cosine_similarity_simd(query_emb, v);
(sim > similarity_floor()).then_some((sim, *i))
})
.collect();
out.sort_by(|a, b| b.0.total_cmp(&a.0));
out.truncate(k);
out
}
}
pub fn similarity_floor() -> f32 {
static FLOOR: std::sync::OnceLock<f32> = std::sync::OnceLock::new();
*FLOOR.get_or_init(|| {
std::env::var("MICROSCOPE_SIM_FLOOR")
.ok()
.and_then(|v| v.trim().parse::<f32>().ok())
.filter(|v| v.is_finite())
.unwrap_or(0.2)
})
}
fn search_with_floor(
query_emb: &[f32],
k: usize,
dim: usize,
data: &[u8],
header: usize,
count: usize,
ids: &[u32],
) -> Vec<(f32, usize)> {
if query_emb.len() != dim {
return vec![];
}
let floor = similarity_floor();
let mut results: Vec<(f32, usize)> = (0..count)
.into_par_iter()
.filter_map(|i| {
let offset = header + count * 4 + i * dim * 4;
let ptr = data[offset..].as_ptr() as *const f32;
let emb = unsafe { std::slice::from_raw_parts(ptr, dim) };
let sim = cosine_similarity_simd(query_emb, emb);
(sim > floor).then_some((sim, ids[i] as usize))
})
.collect();
results.sort_by(|a, b| b.0.total_cmp(&a.0));
results.truncate(k);
results
}
pub fn build_embedding_index(
provider: &dyn EmbeddingProvider,
reader: &crate::MicroscopeReader,
max_depth: u8,
output_path: &Path,
) -> Result<(), String> {
let dim = provider.dimension();
let total_blocks = reader.block_count;
let min_chars = min_embed_chars();
let mut skipped_short = 0usize;
let mut skipped_unencodable = 0usize;
let mut skipped_mojibake = 0usize;
let mut qualifying = Vec::new();
for i in 0..total_blocks {
let h = reader.header(i);
if h.depth > max_depth {
continue;
}
match quality_gate(reader.text(i), min_chars) {
EmbedVerdict::Keep => qualifying.push(i),
EmbedVerdict::Short => skipped_short += 1,
EmbedVerdict::Unencodable => skipped_unencodable += 1,
EmbedVerdict::Mojibake => skipped_mojibake += 1,
}
}
if skipped_short + skipped_unencodable + skipped_mojibake > 0 {
println!(
" Embedding quality gate: -{} short (<{} chars), -{} unencodable, -{} mojibake",
skipped_short, min_chars, skipped_unencodable, skipped_mojibake
);
}
println!(
" Embedding up to {} blocks (D0-D{}, dim={})...",
qualifying.len(),
max_depth,
dim
);
let mut block_ids: Vec<u32> = Vec::with_capacity(qualifying.len());
let mut vectors: Vec<f32> = Vec::with_capacity(qualifying.len() * dim);
let mut failures = 0usize;
let mut first_error: Option<String> = None;
for (n, &i) in qualifying.iter().enumerate() {
let text = reader.text(i);
match provider.embed(text) {
Ok(emb) if emb.len() == dim && emb.iter().any(|&v| v != 0.0) => {
block_ids.push(i as u32);
vectors.extend_from_slice(&emb);
}
Ok(emb) => {
failures += 1;
if first_error.is_none() {
first_error = Some(format!(
"block {} produced {} dims, expected {} ({})",
i,
emb.len(),
dim,
if emb.iter().all(|&v| v == 0.0) {
"all-zero vector"
} else {
"length mismatch"
}
));
}
}
Err(e) => {
failures += 1;
if first_error.is_none() {
first_error = Some(format!("block {}: {}", i, e));
}
}
}
if n.is_multiple_of(1000) {
eprint!("\r Embedded {}/{}", n, qualifying.len());
}
}
eprintln!("\r Embedded {}/{}", qualifying.len(), qualifying.len());
if qualifying.is_empty() {
return Err("no blocks qualified for embedding".into());
}
if block_ids.is_empty() {
return Err(format!(
"all {} blocks failed to embed; first failure: {}",
qualifying.len(),
first_error.unwrap_or_else(|| "unknown".into())
));
}
if failures > 0 {
eprintln!(
" WARN: {} of {} blocks were skipped",
failures,
qualifying.len()
);
}
let mut buf = Vec::with_capacity(HEADER_SIZE + block_ids.len() * 4 + vectors.len() * 4);
buf.extend_from_slice(&(block_ids.len() as u32).to_le_bytes());
buf.extend_from_slice(&(dim as u32).to_le_bytes());
buf.extend_from_slice(&(max_depth as u32).to_le_bytes());
for &id in &block_ids {
buf.extend_from_slice(&id.to_le_bytes());
}
for &v in &vectors {
buf.extend_from_slice(&v.to_le_bytes());
}
let tmp_path = output_path.with_extension("bin.tmp");
fs::write(&tmp_path, &buf).map_err(|e| format!("write embeddings.bin: {}", e))?;
fs::rename(&tmp_path, output_path).map_err(|e| format!("rename embeddings.bin: {}", e))?;
println!(
" embeddings.bin: {:.1} KB ({} stored vectors of {} blocks, dim {})",
buf.len() as f64 / 1024.0,
block_ids.len(),
total_blocks,
dim
);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
#[test]
fn quality_gate_admits_only_retrievable_text() {
assert_eq!(
quality_gate("The user has a pine nut allergy.", 24),
EmbedVerdict::Keep
);
assert_eq!(
quality_gate("The user drinks café lattes every morning.", 24),
EmbedVerdict::Keep
);
assert_eq!(quality_gate("LLM", 24), EmbedVerdict::Short);
assert_eq!(
quality_gate("The user is vegetarian.", 24),
EmbedVerdict::Short,
"23 characters is under the measured 24 floor"
);
assert_eq!(quality_gate("<bin>", 24), EmbedVerdict::Unencodable);
assert_eq!(
quality_gate("[out of bounds]", 24),
EmbedVerdict::Unencodable
);
assert_eq!(
quality_gate("đź§đź§đź§đź§đź§đź§đź§đź§", 24),
EmbedVerdict::Mojibake
);
assert_eq!(
quality_gate("The user is vegetarian.", 20),
EmbedVerdict::Keep,
"MICROSCOPE_MIN_EMBED_CHARS=20 would admit a 23-character fact"
);
}
#[test]
fn min_embed_chars_default_is_the_measured_floor() {
if std::env::var_os("MICROSCOPE_MIN_EMBED_CHARS").is_none() {
assert_eq!(min_embed_chars(), 20);
}
}
#[test]
fn append_embeddings_roundtrip_and_guards() {
let dir = std::env::temp_dir().join("mscope_append_emb_test");
let _ = fs::create_dir_all(&dir);
let path = dir.join(APPEND_EMBEDDINGS_FILE);
let mut side = AppendEmbeddings::new(4);
side.push(0, vec![1.0, 0.0, 0.0, 0.0]);
side.push(1, vec![0.0, 1.0, 0.0, 0.0]);
side.push(7, vec![0.0, 0.0, 0.0, 1.0]);
side.save(&path).expect("save");
let opened = AppendEmbeddings::open(&path, 4).expect("open");
assert_eq!(opened.dim, 4);
assert_eq!(opened.entries.len(), 3);
assert_eq!(
opened.entries[2].0, 7,
"append index survives the round trip"
);
let hits = opened.search(&[1.0, 0.0, 0.0, 0.0], 10);
assert_eq!(hits[0].1, 0);
assert!(hits[0].0 > 0.99, "identical vectors must score ~1.0");
let floor = similarity_floor();
assert!(
hits.iter().all(|&(sim, _)| sim > floor),
"everything returned must clear the floor ({floor})"
);
assert!(AppendEmbeddings::open(&path, 8).is_none());
let junk = dir.join("junk.bin");
fs::write(&junk, b"NOPE\x04\x00\x00\x00\x00\x00\x00\x00").unwrap();
assert!(AppendEmbeddings::open(&junk, 4).is_none());
let mut data = fs::read(&path).unwrap();
data.extend_from_slice(&[0xAB, 0xCD, 0xEF]);
fs::write(&path, &data).unwrap();
let partial = AppendEmbeddings::open(&path, 4).expect("valid prefix survives");
assert_eq!(partial.entries.len(), 3);
let mut bad = AppendEmbeddings::new(4);
bad.push(0, vec![1.0, 0.0]);
assert!(bad.save(&path).is_err());
assert!(partial.search(&[1.0, 0.0], 10).is_empty());
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn append_cache_returns_the_same_content_as_a_fresh_open() {
let dir = std::env::temp_dir().join("mscope_append_cache_parity");
let _ = fs::remove_dir_all(&dir);
fs::create_dir_all(&dir).unwrap();
let path = dir.join(APPEND_EMBEDDINGS_FILE);
let mut side = AppendEmbeddings::new(4);
side.push(0, vec![1.0, 0.0, 0.0, 0.0]);
side.push(3, vec![0.0, 0.5, 0.0, 0.0]);
side.save(&path).unwrap();
let fresh = AppendEmbeddings::open(&path, 4).unwrap();
let cached = open_append_cached(&path, 4).unwrap();
assert_eq!(fresh.entries, cached.entries, "cache must match open()");
assert_eq!(cached.dim, 4);
let again = open_append_cached(&path, 4).unwrap();
assert_eq!(again.entries, fresh.entries);
assert!(open_append_cached(&path, 8).is_none());
assert!(open_append_cached(&dir.join("absent.bin"), 4).is_none());
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn append_cache_invalidates_when_the_file_changes() {
let dir = std::env::temp_dir().join("mscope_append_cache_inval");
let _ = fs::remove_dir_all(&dir);
fs::create_dir_all(&dir).unwrap();
let path = dir.join(APPEND_EMBEDDINGS_FILE);
let mut first = AppendEmbeddings::new(4);
first.push(11, vec![1.0, 0.0, 0.0, 0.0]);
first.save(&path).unwrap();
assert_eq!(open_append_cached(&path, 4).unwrap().entries[0].0, 11);
let mut second = AppendEmbeddings::new(4);
second.push(22, vec![0.0, 1.0, 0.0, 0.0]);
second.push(33, vec![0.0, 0.0, 1.0, 0.0]);
second.push(44, vec![0.0, 0.0, 0.0, 1.0]);
second.save(&path).unwrap();
let after = open_append_cached(&path, 4).unwrap();
assert_eq!(
after.entries.len(),
3,
"a rewrite must be observed, not cached"
);
assert_eq!(after.entries[0].0, 22);
let fresh = AppendEmbeddings::open(&path, 4).unwrap();
assert_eq!(after.entries, fresh.entries, "and must match a fresh open");
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn embedding_index_cache_matches_open() {
let dir = std::env::temp_dir().join("mscope_embed_cache_parity");
let _ = fs::remove_dir_all(&dir);
fs::create_dir_all(&dir).unwrap();
let path = dir.join("embeddings.bin");
let n = build_index(&dir, 3);
let fresh = EmbeddingIndex::open(&path).expect("open");
let cached = open_embedding_cached(&path).expect("cached open");
assert_eq!(cached.block_count(), n);
assert_eq!(cached.dim(), fresh.dim());
assert_eq!(open_embedding_cached(&path).unwrap().block_count(), n);
assert!(open_embedding_cached(&dir.join("nope.bin")).is_none());
let _ = fs::remove_dir_all(&dir);
}
#[cfg(unix)]
#[test]
fn embedding_index_cache_notices_a_rebuild() {
let dir = std::env::temp_dir().join("mscope_embed_cache_rebuild");
let _ = fs::remove_dir_all(&dir);
fs::create_dir_all(&dir).unwrap();
let path = dir.join("embeddings.bin");
let first = build_index(&dir, 2);
assert_eq!(open_embedding_cached(&path).unwrap().block_count(), first);
let bigger = build_index(&dir, 5);
assert_ne!(bigger, first, "the rebuild must actually differ");
let after = open_embedding_cached(&path).expect("reopen after rebuild");
assert_eq!(
after.block_count(),
bigger,
"a rebuilt index must not be served from the stale mapping"
);
let _ = fs::remove_dir_all(&dir);
}
fn build_index(dir: &Path, n: u32) -> usize {
let dim = 4usize;
let mut buf = Vec::new();
buf.extend_from_slice(&n.to_le_bytes());
buf.extend_from_slice(&(dim as u32).to_le_bytes());
buf.extend_from_slice(&3u32.to_le_bytes());
for i in 0..n {
buf.extend_from_slice(&i.to_le_bytes());
let mut v = vec![0.0f32; dim];
v[(i as usize) % dim] = 1.0;
for f in v {
buf.extend_from_slice(&f.to_le_bytes());
}
}
fs::write(dir.join("embeddings.bin"), &buf).unwrap();
n as usize
}
#[test]
fn append_cache_keeps_two_paths_apart() {
let dir = std::env::temp_dir().join("mscope_append_cache_paths");
let _ = fs::remove_dir_all(&dir);
fs::create_dir_all(&dir).unwrap();
let a = dir.join("a.bin");
let b = dir.join("b.bin");
let mut sa = AppendEmbeddings::new(4);
sa.push(101, vec![1.0, 0.0, 0.0, 0.0]);
sa.save(&a).unwrap();
let mut sb = AppendEmbeddings::new(4);
sb.push(202, vec![1.0, 0.0, 0.0, 0.0]);
sb.save(&b).unwrap();
let ca = open_append_cached(&a, 4).unwrap();
let cb = open_append_cached(&b, 4).unwrap();
assert_eq!(ca.entries[0].0, 101, "a must not be served b's vector");
assert_eq!(cb.entries[0].0, 202, "b must not be served a's vector");
assert_eq!(open_append_cached(&a, 4).unwrap().entries[0].0, 101);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_embedding_index_sparse_roundtrip() {
let dir = std::env::temp_dir().join("mscope_emb_sparse_test");
let _ = fs::create_dir_all(&dir);
let path = dir.join("embeddings.bin");
let mut buf = Vec::new();
buf.extend_from_slice(&3u32.to_le_bytes()); buf.extend_from_slice(&4u32.to_le_bytes()); buf.extend_from_slice(&2u32.to_le_bytes()); for &id in &[0u32, 2, 5] {
buf.extend_from_slice(&id.to_le_bytes());
}
for &v in &[1.0f32, 0.0, 0.0, 0.0] {
buf.extend_from_slice(&v.to_le_bytes());
}
for &v in &[0.0f32, 1.0, 0.0, 0.0] {
buf.extend_from_slice(&v.to_le_bytes());
}
for &v in &[0.0f32, 0.0, 1.0, 0.0] {
buf.extend_from_slice(&v.to_le_bytes());
}
let mut f = fs::File::create(&path).unwrap();
f.write_all(&buf).unwrap();
let idx = EmbeddingIndex::open(&path).unwrap();
assert_eq!(idx.block_count(), 3);
assert_eq!(idx.dim(), 4);
assert_eq!(idx.max_depth(), 2);
assert_eq!(idx.embedding(0).unwrap(), &[1.0, 0.0, 0.0, 0.0]);
assert_eq!(idx.embedding(2).unwrap(), &[0.0, 1.0, 0.0, 0.0]);
assert_eq!(idx.embedding(5).unwrap(), &[0.0, 0.0, 1.0, 0.0]);
assert!(idx.embedding(1).is_none());
assert!(idx.embedding(7).is_none());
let results = idx.search(&[1.0, 0.0, 0.0, 0.0], 10);
assert_eq!(results[0].1, 0);
assert!(!results.iter().any(|&(_, i)| i == 7));
assert!(!results.iter().any(|&(_, i)| i == 1));
let trunc = dir.join("truncated.bin");
fs::write(&trunc, &buf[..buf.len() - 3]).unwrap();
assert!(EmbeddingIndex::open(&trunc).is_none());
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_empty_index_open_fails_gracefully() {
let dir = std::env::temp_dir().join("mscope_emb_empty_test");
let _ = fs::create_dir_all(&dir);
let path = dir.join("embeddings.bin");
fs::write(&path, [0u8; 4]).unwrap();
assert!(EmbeddingIndex::open(&path).is_none());
let _ = fs::remove_dir_all(&dir);
}
}