Skip to content

Commit 615093b

Browse files
committed
test(regression): add standalone dataset determinism test target with bit-exact integer PRNG
1 parent 945cff3 commit 615093b

3 files changed

Lines changed: 78 additions & 67 deletions

File tree

‎cpp/test/CMakeLists.txt‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ endmacro()
2323
# Phase 1 Test Target: CPU Features & Build Verification
2424
add_deglib_test(test_cpu_features src/test_cpu_features.cpp)
2525

26+
# Dataset Determinism Regression test
27+
add_deglib_test(test_dataset_determinism src/regression/test_dataset_determinism.cpp)
28+
2629
# Metric Regression tests
2730
add_deglib_test(test_l2_regression src/regression/metric/test_l2_regression.cpp)
2831
add_deglib_test(test_innerproduct_regression src/regression/metric/test_innerproduct_regression.cpp)
@@ -32,7 +35,7 @@ add_deglib_test(test_l2_uint8_regression src/regression/metric/test_l2_uint8_reg
3235
add_deglib_test(test_l2_builder_regression src/regression/builder/test_l2_builder_regression.cpp)
3336

3437
# Meta target to build all regression tests
35-
add_custom_target(test_regression DEPENDS test_l2_regression test_innerproduct_regression test_l2_uint8_regression test_l2_builder_regression)
38+
add_custom_target(test_regression DEPENDS test_dataset_determinism test_l2_regression test_innerproduct_regression test_l2_uint8_regression test_l2_builder_regression)
3639

3740

3841

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#include "test_regression.h"
2+
3+
TEST(DeglibDatasetDeterminism, ClusteredDatasetBitExactness)
4+
{
5+
const size_t dim = 128;
6+
const size_t base_count = 100000;
7+
const size_t query_count = 100;
8+
const size_t num_clusters = 1000;
9+
10+
std::vector<float> base_data;
11+
std::vector<float> query_data;
12+
generate_synthetic_clustered_dataset(base_count, dim, base_data, query_data, query_count, num_clusters);
13+
14+
const uint64_t base_hash = float_vector_checksum(base_data);
15+
const uint64_t query_hash = float_vector_checksum(query_data);
16+
17+
auto gt_l2 = compute_groundtruth_l2(base_data, base_count, query_data, query_count, dim, 10);
18+
const uint64_t gt_l2_hash = groundtruth_checksum(gt_l2);
19+
20+
auto gt_ip = compute_groundtruth_innerproduct(base_data, base_count, query_data, query_count, dim, 10);
21+
const uint64_t gt_ip_hash = groundtruth_checksum(gt_ip);
22+
23+
std::cout << "[DatasetDeterminism] base_hash = 0x" << std::hex << base_hash << std::dec << std::endl;
24+
std::cout << "[DatasetDeterminism] query_hash = 0x" << std::hex << query_hash << std::dec << std::endl;
25+
std::cout << "[DatasetDeterminism] gt_l2_hash = 0x" << std::hex << gt_l2_hash << std::dec << std::endl;
26+
std::cout << "[DatasetDeterminism] gt_ip_hash = 0x" << std::hex << gt_ip_hash << std::dec << std::endl;
27+
28+
// Hardcoded expected hashes computed on Windows
29+
// Checksum verification ensures 100% bit-exact dataset generation and groundtruth across OS/compilers.
30+
EXPECT_EQ(base_hash, 0xef1c7686c6a529f4ULL) << "base_data checksum mismatch across platforms!";
31+
EXPECT_EQ(query_hash, 0xea1c88f507eeb188ULL) << "query_data checksum mismatch across platforms!";
32+
EXPECT_EQ(gt_l2_hash, 0xe04ea5bc6428cc3dULL) << "gt_l2 checksum mismatch across platforms!";
33+
EXPECT_EQ(gt_ip_hash, 0x10c26c33619a5928ULL) << "gt_ip checksum mismatch across platforms!";
34+
}
35+
36+
TEST(DeglibDatasetDeterminism, Uint8ClusteredDatasetBitExactness)
37+
{
38+
const size_t dim = 128;
39+
const size_t base_count = 100000;
40+
const size_t query_count = 100;
41+
const size_t num_clusters = 1000;
42+
43+
std::vector<uint8_t> base_data;
44+
std::vector<uint8_t> query_data;
45+
generate_synthetic_clustered_dataset_uint8(base_count, dim, base_data, query_data, query_count, num_clusters);
46+
47+
const uint64_t base_hash = fnv1a_64(base_data.data(), base_data.size());
48+
const uint64_t query_hash = fnv1a_64(query_data.data(), query_data.size());
49+
50+
auto gt_u8 = compute_groundtruth_l2_uint8(base_data, base_count, query_data, query_count, dim, 10);
51+
const uint64_t gt_u8_hash = groundtruth_checksum(gt_u8);
52+
53+
std::cout << "[DatasetDeterminism] uint8_base_hash = 0x" << std::hex << base_hash << std::dec << std::endl;
54+
std::cout << "[DatasetDeterminism] uint8_query_hash = 0x" << std::hex << query_hash << std::dec << std::endl;
55+
std::cout << "[DatasetDeterminism] gt_u8_hash = 0x" << std::hex << gt_u8_hash << std::dec << std::endl;
56+
57+
EXPECT_EQ(base_hash, 0x4d94d32f77a245daULL) << "uint8_base_data checksum mismatch across platforms!";
58+
EXPECT_EQ(query_hash, 0x5493b1b61d1b8a94ULL) << "uint8_query_data checksum mismatch across platforms!";
59+
EXPECT_EQ(gt_u8_hash, 0xde241e831af209fcULL) << "gt_u8 checksum mismatch across platforms!";
60+
}

‎cpp/test/src/regression/test_regression.h‎

Lines changed: 14 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -84,11 +84,14 @@ inline static uint32_t deglib_prng_next(uint32_t& state) {
8484

8585
inline static float deglib_prng_float(uint32_t& state, float min_val, float max_val) {
8686
uint32_t val = deglib_prng_next(state) >> 8; // 24-bit integer
87-
float u = static_cast<float>(val) * (1.0f / 16777215.0f);
88-
return min_val + u * (max_val - min_val);
87+
float u = static_cast<float>(val) / 16777215.0f;
88+
float range = max_val - min_val;
89+
return min_val + u * range;
8990
}
9091

9192
// Generate cross-platform deterministic clustered dataset (Gaussian Mixture with fixed seed)
93+
// Uses pure 32-bit integer arithmetic and exact integer-to-float conversion to guarantee
94+
// 100% bit-exact float vectors across MSVC, GCC, and Clang on all CPU architectures.
9295
inline static void generate_synthetic_clustered_dataset(size_t count, size_t dim, std::vector<float>& base,
9396
std::vector<float>& query, size_t query_count,
9497
size_t num_clusters = 20)
@@ -97,13 +100,14 @@ inline static void generate_synthetic_clustered_dataset(size_t count, size_t dim
97100
query.resize(query_count * dim);
98101

99102
uint32_t rng_state = 42;
100-
std::vector<std::vector<float>> centroids(num_clusters, std::vector<float>(dim));
103+
std::vector<std::vector<int32_t>> centroids(num_clusters, std::vector<int32_t>(dim));
101104

102105
for (size_t c = 0; c < num_clusters; ++c)
103106
{
104107
for (size_t d = 0; d < dim; ++d)
105108
{
106-
centroids[c][d] = deglib_prng_float(rng_state, -1000.0f, 1000.0f);
109+
uint32_t val = deglib_prng_next(rng_state);
110+
centroids[c][d] = static_cast<int32_t>(val % 2001) - 1000; // [-1000, 1000]
107111
}
108112
}
109113

@@ -112,8 +116,9 @@ inline static void generate_synthetic_clustered_dataset(size_t count, size_t dim
112116
size_t c = i % num_clusters;
113117
for (size_t d = 0; d < dim; ++d)
114118
{
115-
float noise = deglib_prng_float(rng_state, -100.0f, 100.0f);
116-
base[i * dim + d] = centroids[c][d] + noise;
119+
uint32_t val = deglib_prng_next(rng_state);
120+
int32_t noise = static_cast<int32_t>(val % 201) - 100; // [-100, 100]
121+
base[i * dim + d] = static_cast<float>(centroids[c][d] + noise);
117122
}
118123
}
119124

@@ -122,8 +127,9 @@ inline static void generate_synthetic_clustered_dataset(size_t count, size_t dim
122127
size_t c = q % num_clusters;
123128
for (size_t d = 0; d < dim; ++d)
124129
{
125-
float noise = deglib_prng_float(rng_state, -100.0f, 100.0f);
126-
query[q * dim + d] = centroids[c][d] + noise;
130+
uint32_t val = deglib_prng_next(rng_state);
131+
int32_t noise = static_cast<int32_t>(val % 201) - 100; // [-100, 100]
132+
query[q * dim + d] = static_cast<float>(centroids[c][d] + noise);
127133
}
128134
}
129135
}
@@ -381,63 +387,5 @@ inline static uint64_t groundtruth_checksum(const std::vector<std::vector<uint32
381387
return hash;
382388
}
383389

384-
TEST(DeglibDatasetDeterminism, ClusteredDatasetBitExactness)
385-
{
386-
const size_t dim = 128;
387-
const size_t base_count = 100000;
388-
const size_t query_count = 100;
389-
const size_t num_clusters = 1000;
390-
391-
std::vector<float> base_data;
392-
std::vector<float> query_data;
393-
generate_synthetic_clustered_dataset(base_count, dim, base_data, query_data, query_count, num_clusters);
394-
395-
const uint64_t base_hash = float_vector_checksum(base_data);
396-
const uint64_t query_hash = float_vector_checksum(query_data);
397-
398-
auto gt_l2 = compute_groundtruth_l2(base_data, base_count, query_data, query_count, dim, 10);
399-
const uint64_t gt_l2_hash = groundtruth_checksum(gt_l2);
400-
401-
auto gt_ip = compute_groundtruth_innerproduct(base_data, base_count, query_data, query_count, dim, 10);
402-
const uint64_t gt_ip_hash = groundtruth_checksum(gt_ip);
403-
404-
std::cout << "[DatasetDeterminism] base_hash = 0x" << std::hex << base_hash << std::dec << std::endl;
405-
std::cout << "[DatasetDeterminism] query_hash = 0x" << std::hex << query_hash << std::dec << std::endl;
406-
std::cout << "[DatasetDeterminism] gt_l2_hash = 0x" << std::hex << gt_l2_hash << std::dec << std::endl;
407-
std::cout << "[DatasetDeterminism] gt_ip_hash = 0x" << std::hex << gt_ip_hash << std::dec << std::endl;
408-
409-
// Hardcoded expected hashes computed on Windows
410-
// Checksum verification ensures 100% bit-exact dataset generation and groundtruth across OS/compilers.
411-
EXPECT_EQ(base_hash, 0x9843814b67933ae7ULL) << "base_data checksum mismatch across platforms!";
412-
EXPECT_EQ(query_hash, 0xf632af44f678450dULL) << "query_data checksum mismatch across platforms!";
413-
EXPECT_EQ(gt_l2_hash, 0xecfc8c14d4c8a2dcULL) << "gt_l2 checksum mismatch across platforms!";
414-
EXPECT_EQ(gt_ip_hash, 0xe37a489e10fb4a5fULL) << "gt_ip checksum mismatch across platforms!";
415-
}
416-
417-
TEST(DeglibDatasetDeterminism, Uint8ClusteredDatasetBitExactness)
418-
{
419-
const size_t dim = 128;
420-
const size_t base_count = 100000;
421-
const size_t query_count = 100;
422-
const size_t num_clusters = 1000;
423-
424-
std::vector<uint8_t> base_data;
425-
std::vector<uint8_t> query_data;
426-
generate_synthetic_clustered_dataset_uint8(base_count, dim, base_data, query_data, query_count, num_clusters);
427-
428-
const uint64_t base_hash = fnv1a_64(base_data.data(), base_data.size());
429-
const uint64_t query_hash = fnv1a_64(query_data.data(), query_data.size());
430-
431-
auto gt_u8 = compute_groundtruth_l2_uint8(base_data, base_count, query_data, query_count, dim, 10);
432-
const uint64_t gt_u8_hash = groundtruth_checksum(gt_u8);
433-
434-
std::cout << "[DatasetDeterminism] uint8_base_hash = 0x" << std::hex << base_hash << std::dec << std::endl;
435-
std::cout << "[DatasetDeterminism] uint8_query_hash = 0x" << std::hex << query_hash << std::dec << std::endl;
436-
std::cout << "[DatasetDeterminism] gt_u8_hash = 0x" << std::hex << gt_u8_hash << std::dec << std::endl;
437-
438-
EXPECT_EQ(base_hash, 0x4d94d32f77a245daULL) << "uint8_base_data checksum mismatch across platforms!";
439-
EXPECT_EQ(query_hash, 0x5493b1b61d1b8a94ULL) << "uint8_query_data checksum mismatch across platforms!";
440-
EXPECT_EQ(gt_u8_hash, 0xde241e831af209fcULL) << "gt_u8 checksum mismatch across platforms!";
441-
}
442390

443391

0 commit comments

Comments
 (0)