1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
//! Determinism test for the parallel multi-segment fan-out (Issue #926).
//!
//! `SegmentFanoutSearcher::search` runs its per-segment probes concurrently
//! on native targets. This test pins that repeated identical searches over
//! a multi-segment fixture (stale duplicates + a deletion) return
//! bit-identical results — parallel probing must not leak scheduling
//! nondeterminism into the merged order. Cross-segment score correctness
//! itself is pinned by `segment_score_comparability_test.rs` (#927), which
//! exercises the same fan-out (parallel on native) against brute force.
use std::sync::Arc;
use laurus::storage::Storage;
use laurus::storage::memory::{MemoryStorage, MemoryStorageConfig};
use laurus::vector::index::VectorIndex;
use laurus::vector::index::config::HnswIndexConfig;
use laurus::vector::index::hnsw::segmented::SegmentedHnswIndex;
use laurus::vector::search::searcher::{VectorIndexQuery, VectorIndexQueryParams};
use laurus::vector::{DistanceMetric, Vector};
const DIM: usize = 8;
const TOP_K: usize = 5;
fn config() -> HnswIndexConfig {
HnswIndexConfig {
dimension: DIM,
m: 8,
ef_construction: 64,
normalize_vectors: false,
distance_metric: DistanceMetric::Euclidean,
segmented: true,
..Default::default()
}
}
/// A vector whose components all equal `level * 10.0` — grid spacing keeps
/// every pairwise distance far outside quantization error.
fn grid(level: f32) -> Vector {
Vector::new(vec![level * 10.0; DIM])
}
fn query(vector: &Vector, ef: usize) -> VectorIndexQuery {
VectorIndexQuery {
query: vector.clone(),
params: VectorIndexQueryParams {
top_k: TOP_K,
ef_search: Some(ef),
..Default::default()
},
field_name: Some("v".to_string()),
filter: None,
}
}
/// Build a 4-segment index with cross-segment stale duplicates and one
/// deletion (the same shape as #927's regression fixture).
fn build_fixture() -> SegmentedHnswIndex {
let storage = Arc::new(MemoryStorage::new(MemoryStorageConfig::default()));
let index =
SegmentedHnswIndex::open_or_create(storage as Arc<dyn Storage>, "vi", config()).unwrap();
// 4 commits = 4 sealed segments (no merge trigger in this test).
// Segment 1: docs 0..10 at levels 0..10.
// Segment 2: docs 10..20 at levels 10..20.
// Segment 3: docs 20..30 at levels 20..30, PLUS doc 3 re-added at
// level 25.5 (stale copy of doc 3 remains in segment 1).
// Segment 4: doc 7 re-added at level 12.5 (stale copy in segment 1).
let batches: Vec<Vec<(u64, String, Vector)>> = vec![
(0..10u64)
.map(|i| (i, "v".to_string(), grid(i as f32)))
.collect(),
(10..20u64)
.map(|i| (i, "v".to_string(), grid(i as f32)))
.collect(),
(20..30u64)
.map(|i| (i, "v".to_string(), grid(i as f32)))
.chain(std::iter::once((3u64, "v".to_string(), grid(25.5))))
.collect(),
vec![(7u64, "v".to_string(), grid(12.5))],
];
for batch in batches {
let mut w = index.writer().unwrap();
w.add_vectors(batch).unwrap();
w.commit().unwrap();
}
// Delete doc 15 (level 15) — must never appear in results.
index.soft_delete_document(15).unwrap();
index
}
/// Repeated identical searches must return identical result lists —
/// guards against nondeterministic merge order under parallel probing.
#[test]
fn parallel_fanout_is_deterministic_across_runs() {
let index = build_fixture();
let searcher = index.searcher().unwrap();
let reference: Vec<(u64, u32)> = searcher
.search(&query(&grid(13.7), 256))
.unwrap()
.results
.iter()
.map(|r| (r.doc_id, r.similarity.to_bits()))
.collect();
assert!(!reference.is_empty());
for run in 0..5 {
let again: Vec<(u64, u32)> = searcher
.search(&query(&grid(13.7), 256))
.unwrap()
.results
.iter()
.map(|r| (r.doc_id, r.similarity.to_bits()))
.collect();
assert_eq!(reference, again, "run {run}: results must be bit-identical");
}
}