From 620f8d348ed65f6033842dcbd992337efb8a1afb Mon Sep 17 00:00:00 2001 From: gouyt13clear Date: Wed, 16 Sep 2026 14:38:59 +0800 Subject: [PATCH] fix: restore QuantizedGraph source compatibility --- CMakeLists.txt | 2 +- docs/docs/index/qg.md | 13 +- include/rabitqlib/index/symqg/qg.hpp | 18 ++- include/rabitqlib/index/symqg/qg_builder.hpp | 6 +- pyproject.toml | 2 +- python_bindings/symqg_bindings.cpp | 6 +- sample/cpp/symqg_indexing.cpp | 2 +- sample/cpp/symqg_querying.cpp | 2 +- src/index/qg.cpp | 96 ++++++++------- tests/unit/rabitqlib/index/qg_test.cpp | 120 ++++++++++++------- 10 files changed, 162 insertions(+), 105 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 383a77d..556fb6c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.15) -project(RaBitQLib VERSION 0.3.5 LANGUAGES CXX) +project(RaBitQLib VERSION 0.3.6 LANGUAGES CXX) include(CMakePackageConfigHelpers) include(GNUInstallDirs) diff --git a/docs/docs/index/qg.md b/docs/docs/index/qg.md index d6833c2..8531669 100644 --- a/docs/docs/index/qg.md +++ b/docs/docs/index/qg.md @@ -40,18 +40,19 @@ window; `ef` controls the query search window. Python defaults to one thread. ### C++ The C++ API uses the float-only `rabitqlib::symqg::QuantizedGraph` and `QGBuilder`. -`QuantizedGraph` is a non-template class; use `QuantizedGraph` instead of -`QuantizedGraph`. Its implementation is compiled in `src/index/qg.cpp`: +`QuantizedGraph` retains the C++ template API, with its implementation +compiled in `src/index/qg.cpp`. C++17 variable declarations can also omit `` +through class template argument deduction: ```cpp -QuantizedGraph( +QuantizedGraph( size_t num, size_t dim, size_t max_deg, MetricType metric_type = METRIC_L2, RotatorType rotator_type = RotatorType::FhtKacRotator, size_t quantization_bits = 0 ); QGBuilder( - QuantizedGraph& index, uint32_t ef_build, const float* data, + QuantizedGraph& index, uint32_t ef_build, const float* data, size_t num_threads = std::numeric_limits::max(), QGInitialization init = QGInitialization::PiPNN ); @@ -66,7 +67,7 @@ The builder handles initialization internally. using namespace rabitqlib::symqg; // data contains rows * cols floats. -QuantizedGraph qg(rows, cols, 32); +QuantizedGraph qg(rows, cols, 32); { QGBuilder builder(qg, 200, data.data(), 32, QGInitialization::PiPNN); builder.build(); @@ -99,7 +100,7 @@ C++ search accepts one vector in the original input dimension and writes `k` IDs and distances: ```cpp -QuantizedGraph qg; +QuantizedGraph qg; qg.load("qg_example.index"); qg.set_ef(100); diff --git a/include/rabitqlib/index/symqg/qg.hpp b/include/rabitqlib/index/symqg/qg.hpp index f7799ad..2758bf6 100644 --- a/include/rabitqlib/index/symqg/qg.hpp +++ b/include/rabitqlib/index/symqg/qg.hpp @@ -35,7 +35,11 @@ class QuantizedQuery { [[nodiscard]] float g_add() const; }; -class QuantizedGraph { +template +class QuantizedGraph; + +template <> +class QuantizedGraph { friend class QGBuilder; friend struct QGConstructionTestAccess; @@ -187,4 +191,16 @@ class QuantizedGraph { ); }; +// Preserve C++17 deduction for callers that omit the float template argument. +QuantizedGraph()->QuantizedGraph; +QuantizedGraph( + size_t, + size_t, + size_t, + MetricType = METRIC_L2, + RotatorType = RotatorType::FhtKacRotator, + size_t = 0 +) + ->QuantizedGraph; + } // namespace rabitqlib::symqg diff --git a/include/rabitqlib/index/symqg/qg_builder.hpp b/include/rabitqlib/index/symqg/qg_builder.hpp index a9abdab..42bb669 100644 --- a/include/rabitqlib/index/symqg/qg_builder.hpp +++ b/include/rabitqlib/index/symqg/qg_builder.hpp @@ -32,7 +32,7 @@ class QGBuilder { friend struct QGConstructionTestAccess; private: - QuantizedGraph& qg_; + QuantizedGraph& qg_; size_t ef_build_; // size of search pool for indexing size_t num_threads_; // number of threads used for indexing size_t num_nodes_; // num of data points @@ -59,7 +59,7 @@ class QGBuilder { void initialize_storage(const float* data); - QGBuilder(QuantizedGraph& index, uint32_t ef_build, size_t num_threads) + QGBuilder(QuantizedGraph& index, uint32_t ef_build, size_t num_threads) : qg_{index} , ef_build_{ef_build} , num_threads_{std::max(1, std::min(num_threads, total_threads()))} @@ -69,7 +69,7 @@ class QGBuilder { public: explicit QGBuilder( - QuantizedGraph& index, + QuantizedGraph& index, uint32_t ef_build, const float* data, size_t num_threads = std::numeric_limits::max(), diff --git a/pyproject.toml b/pyproject.toml index d0dacc0..55572d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build" [project] name = "rabitqlib" -version = "0.3.5" +version = "0.3.6" description = "RaBitQ Python bindings for HNSW, IVF, and SymQG" readme = "README.md" requires-python = ">=3.11" diff --git a/python_bindings/symqg_bindings.cpp b/python_bindings/symqg_bindings.cpp index 60dfe48..9a54bf0 100644 --- a/python_bindings/symqg_bindings.cpp +++ b/python_bindings/symqg_bindings.cpp @@ -66,7 +66,7 @@ class SymqgIndex { } num_points_ = static_cast(data_array.shape(0)); - index_ = std::make_unique( + index_ = std::make_unique>( num_points_, dim_, max_degree_, @@ -167,7 +167,7 @@ class SymqgIndex { static SymqgIndex load(const std::string& path) { SymqgIndex wrapper; - wrapper.index_ = std::make_unique(); + wrapper.index_ = std::make_unique>(); wrapper.index_->load(path.c_str()); wrapper.num_points_ = wrapper.index_->num_vertices(); wrapper.dim_ = wrapper.index_->dimension(); @@ -194,7 +194,7 @@ class SymqgIndex { rabitqlib::MetricType metric_ = rabitqlib::METRIC_L2; size_t quantization_bits_ = 0; bool built_ = false; - std::unique_ptr index_; + std::unique_ptr> index_; }; } // namespace rabitqlib::python_bindings diff --git a/sample/cpp/symqg_indexing.cpp b/sample/cpp/symqg_indexing.cpp index 2a8348f..6853bab 100644 --- a/sample/cpp/symqg_indexing.cpp +++ b/sample/cpp/symqg_indexing.cpp @@ -12,7 +12,7 @@ #include "rabitqlib/utils/stopw.hpp" using PID = rabitqlib::PID; -using index_type = rabitqlib::symqg::QuantizedGraph; +using index_type = rabitqlib::symqg::QuantizedGraph; using data_type = rabitqlib::RowMajorArray; using gt_type = rabitqlib::RowMajorArray; diff --git a/sample/cpp/symqg_querying.cpp b/sample/cpp/symqg_querying.cpp index 8ea343b..4453bba 100644 --- a/sample/cpp/symqg_querying.cpp +++ b/sample/cpp/symqg_querying.cpp @@ -7,7 +7,7 @@ #include "rabitqlib/utils/stopw.hpp" using PID = rabitqlib::PID; -using index_type = rabitqlib::symqg::QuantizedGraph; +using index_type = rabitqlib::symqg::QuantizedGraph; using data_type = rabitqlib::RowMajorArray; using gt_type = rabitqlib::RowMajorArray; diff --git a/src/index/qg.cpp b/src/index/qg.cpp index 05d4bde..1dbd3c6 100644 --- a/src/index/qg.cpp +++ b/src/index/qg.cpp @@ -46,97 +46,101 @@ const float* QuantizedQuery::rotated_query() const { return rotated_query_; } float QuantizedQuery::k1xsumq() const { return k1xsumq_; } float QuantizedQuery::g_add() const { return g_add_; } -size_t QuantizedGraph::checked_add(size_t lhs, size_t rhs) { +size_t QuantizedGraph::checked_add(size_t lhs, size_t rhs) { if (lhs > std::numeric_limits::max() - rhs) { throw std::length_error("QuantizedGraph storage size exceeds size_t"); } return lhs + rhs; } -size_t QuantizedGraph::checked_multiply(size_t lhs, size_t rhs) { +size_t QuantizedGraph::checked_multiply(size_t lhs, size_t rhs) { if (lhs != 0 && rhs > std::numeric_limits::max() / lhs) { throw std::length_error("QuantizedGraph storage size exceeds size_t"); } return lhs * rhs; } -size_t QuantizedGraph::padded_dimension(size_t dim) { +size_t QuantizedGraph::padded_dimension(size_t dim) { return (checked_add(dim, 63) / 64) * 64; } -char* QuantizedGraph::get_row_data(PID data_id) { +char* QuantizedGraph::get_row_data(PID data_id) { return reinterpret_cast(get_vector(data_id)); } -const char* QuantizedGraph::get_row_data(PID data_id) const { +const char* QuantizedGraph::get_row_data(PID data_id) const { return reinterpret_cast(get_vector(data_id)); } -float* QuantizedGraph::get_vector(PID data_id) { +float* QuantizedGraph::get_vector(PID data_id) { return data_.data() + ((row_offset_ / sizeof(float)) * data_id); } -const float* QuantizedGraph::get_vector(PID data_id) const { +const float* QuantizedGraph::get_vector(PID data_id) const { return data_.data() + ((row_offset_ / sizeof(float)) * data_id); } -char* QuantizedGraph::get_quantized_vector(PID data_id) { return get_row_data(data_id); } +char* QuantizedGraph::get_quantized_vector(PID data_id) { + return get_row_data(data_id); +} -const char* QuantizedGraph::get_quantized_vector(PID data_id) const { +const char* QuantizedGraph::get_quantized_vector(PID data_id) const { return get_row_data(data_id); } -char* QuantizedGraph::get_batch_data(PID data_id) { +char* QuantizedGraph::get_batch_data(PID data_id) { return get_row_data(data_id) + batch_data_offset_; } -const char* QuantizedGraph::get_batch_data(PID data_id) const { +const char* QuantizedGraph::get_batch_data(PID data_id) const { return get_row_data(data_id) + batch_data_offset_; } -rabitqlib::detail::PackedArrayView QuantizedGraph::get_neighbors(PID data_id) { +rabitqlib::detail::PackedArrayView QuantizedGraph::get_neighbors(PID data_id) { return rabitqlib::detail::PackedArrayView( get_row_data(data_id) + neighbor_offset_ ); } -rabitqlib::detail::ConstPackedArrayView QuantizedGraph::get_neighbors(PID data_id +rabitqlib::detail::ConstPackedArrayView QuantizedGraph::get_neighbors( + PID data_id ) const { return rabitqlib::detail::ConstPackedArrayView( get_row_data(data_id) + neighbor_offset_ ); } -size_t QuantizedGraph::num_vertices() const { return this->num_points_; } +size_t QuantizedGraph::num_vertices() const { return this->num_points_; } -size_t QuantizedGraph::dimension() const { return this->dim_; } +size_t QuantizedGraph::dimension() const { return this->dim_; } -size_t QuantizedGraph::degree_bound() const { return this->degree_bound_; } +size_t QuantizedGraph::degree_bound() const { return this->degree_bound_; } -PID QuantizedGraph::entry_point() const { return this->entry_point_; } +PID QuantizedGraph::entry_point() const { return this->entry_point_; } -MetricType QuantizedGraph::metric_type() const { return this->metric_type_; } +MetricType QuantizedGraph::metric_type() const { return this->metric_type_; } -size_t QuantizedGraph::quantization_bits() const { return this->quantization_bits_; } +size_t QuantizedGraph::quantization_bits() const { return this->quantization_bits_; } -bool QuantizedGraph::is_quantized() const { return quantization_bits_ != 0; } +bool QuantizedGraph::is_quantized() const { return quantization_bits_ != 0; } -void QuantizedGraph::set_ep(PID entry) { +void QuantizedGraph::set_ep(PID entry) { if (entry >= num_points_) { throw std::invalid_argument("QuantizedGraph entry point is out of range"); } entry_point_ = entry; } -QuantizedGraph::QuantizedGraph() = default; +QuantizedGraph::QuantizedGraph() = default; -QuantizedGraph::~QuantizedGraph() = default; +QuantizedGraph::~QuantizedGraph() = default; -QuantizedGraph::QuantizedGraph(QuantizedGraph&&) noexcept = default; +QuantizedGraph::QuantizedGraph(QuantizedGraph&&) noexcept = default; -QuantizedGraph& QuantizedGraph::operator=(QuantizedGraph&&) noexcept = default; +QuantizedGraph& QuantizedGraph::operator=(QuantizedGraph&&) noexcept = + default; -QuantizedGraph::QuantizedGraph( +QuantizedGraph::QuantizedGraph( size_t num, size_t dim, size_t max_deg, @@ -156,7 +160,7 @@ QuantizedGraph::QuantizedGraph( initialize(); } -void QuantizedGraph::validate_configuration() const { +void QuantizedGraph::validate_configuration() const { validate_metric_type(metric_type_); if (dim_ == 0) { throw std::invalid_argument("QuantizedGraph dimension must be positive"); @@ -190,7 +194,7 @@ void QuantizedGraph::validate_configuration() const { } } -void QuantizedGraph::copy_vectors(const float* data, size_t num_threads) { +void QuantizedGraph::copy_vectors(const float* data, size_t num_threads) { const int thread_count = static_cast(num_threads); if (quantization_bits_ != 0) { if (centroid_.size() != padded_dim_) { @@ -237,7 +241,7 @@ void QuantizedGraph::copy_vectors(const float* data, size_t num_threads) { } } -void QuantizedGraph::set_quantization_centroid(const float* centroid) { +void QuantizedGraph::set_quantization_centroid(const float* centroid) { if (quantization_bits_ == 0) { return; } @@ -245,7 +249,7 @@ void QuantizedGraph::set_quantization_centroid(const float* centroid) { rotator_->rotate(centroid, centroid_.data()); } -void QuantizedGraph::save(const char* filename) const { +void QuantizedGraph::save(const char* filename) const { if (!ready_ || rotator_ == nullptr) { throw std::logic_error("QuantizedGraph must be built or loaded before save"); } @@ -298,7 +302,7 @@ void QuantizedGraph::save(const char* filename) const { output.close(); } -void QuantizedGraph::load(const char* filename) { +void QuantizedGraph::load(const char* filename) { if (filename == nullptr || filename[0] == '\0') { throw std::invalid_argument("QuantizedGraph load filename must not be empty"); } @@ -345,7 +349,7 @@ void QuantizedGraph::load(const char* filename) { input.seekg(0); } - QuantizedGraph loaded; + QuantizedGraph loaded; size_t stored_padded_dim = 0; read_value(loaded.num_points_, "point count"); read_value(loaded.degree_bound_, "degree bound"); @@ -424,14 +428,14 @@ void QuantizedGraph::load(const char* filename) { *this = std::move(loaded); } -void QuantizedGraph::set_ef(size_t cur_ef) { +void QuantizedGraph::set_ef(size_t cur_ef) { if (cur_ef == 0) { throw std::invalid_argument("QuantizedGraph ef must be positive"); } this->ef_ = cur_ef; } -void QuantizedGraph::search( +void QuantizedGraph::search( const float* __restrict__ query, uint32_t k, uint32_t* __restrict__ results, @@ -495,7 +499,7 @@ void QuantizedGraph::search( res_pool.copy_results(results, dists); } -void QuantizedGraph::prepare_query( +void QuantizedGraph::prepare_query( const float* query, std::vector& rotated_query, std::optional& quantized_query @@ -509,7 +513,7 @@ void QuantizedGraph::prepare_query( } } -float QuantizedGraph::point_distance( +float QuantizedGraph::point_distance( const float* raw_query, const QuantizedQuery* quantized_query, PID data_id ) const { if (quantized_query != nullptr) { @@ -520,7 +524,7 @@ float QuantizedGraph::point_distance( // Scan a data row and store estimated neighbor distances. The caller scores the current // vertex from either its raw vector (vanilla QG) or its 4/8-bit code (qg-quant). -void QuantizedGraph::scan_neighbors( +void QuantizedGraph::scan_neighbors( const BatchQuery& q_obj, PID data_id, float* est_dist, @@ -559,7 +563,7 @@ void QuantizedGraph::scan_neighbors( } } -void QuantizedGraph::update_results( +void QuantizedGraph::update_results( buffer::SearchBuffer& result_pool, VisitedSet& vis, const float* query, @@ -591,7 +595,7 @@ void QuantizedGraph::update_results( } // initialize const offsets & data array -void QuantizedGraph::initialize_layout() { +void QuantizedGraph::initialize_layout() { if (quantization_bits_ == 0) { batch_data_offset_ = checked_multiply(dim_, sizeof(float)); } else { @@ -612,7 +616,7 @@ void QuantizedGraph::initialize_layout() { checked_add(neighbor_offset_, checked_multiply(degree_bound_, sizeof(PID))); } -void QuantizedGraph::initialize() { +void QuantizedGraph::initialize() { padded_dim_ = padded_dimension(dim_); rotator_.reset(choose_rotator(dim_, rotator_type_, padded_dim_)); @@ -630,7 +634,8 @@ void QuantizedGraph::initialize() { } } -float QuantizedGraph::quantized_distance(const QuantizedQuery& query, PID data_id) const { +float QuantizedGraph::quantized_distance(const QuantizedQuery& query, PID data_id) + const { ConstExDataMap data( get_quantized_vector(data_id), padded_dim_, quantization_bits_ ); @@ -647,7 +652,8 @@ float QuantizedGraph::quantized_distance(const QuantizedQuery& query, PID data_i ); } -void QuantizedGraph::reconstruct_quantized_vector(PID data_id, float* reconstructed) const { +void QuantizedGraph::reconstruct_quantized_vector(PID data_id, float* reconstructed) + const { ConstExDataMap data( get_quantized_vector(data_id), padded_dim_, quantization_bits_ ); @@ -678,7 +684,7 @@ void QuantizedGraph::reconstruct_quantized_vector(PID data_id, float* reconstruc // Construction sources come from owned raw rows or from the existing RaBitQ codes. // Reconstructed sources are already rotated; never pass them through prepare_query. -const float* QuantizedGraph::prepare_build_query( +const float* QuantizedGraph::prepare_build_query( PID id, std::vector& rotated, std::optional& prepared ) const { if (is_quantized()) { @@ -692,7 +698,7 @@ const float* QuantizedGraph::prepare_build_query( } // find candidate neighbors for cur_id, exclude the vertex itself -void QuantizedGraph::find_candidates( +void QuantizedGraph::find_candidates( PID cur_id, size_t search_ef, std::vector>& results, @@ -731,7 +737,7 @@ void QuantizedGraph::find_candidates( } // based on new neighbor lists to update quantization code and factors -void QuantizedGraph::update_qg( +void QuantizedGraph::update_qg( PID cur_id, const std::vector>& new_neighbors ) { size_t cur_degree = new_neighbors.size(); diff --git a/tests/unit/rabitqlib/index/qg_test.cpp b/tests/unit/rabitqlib/index/qg_test.cpp index be038b3..f5268df 100644 --- a/tests/unit/rabitqlib/index/qg_test.cpp +++ b/tests/unit/rabitqlib/index/qg_test.cpp @@ -35,7 +35,7 @@ namespace rabitqlib::symqg { struct QGConstructionTestAccess { - static void check_contiguous_rows(QuantizedGraph& graph) { + static void check_contiguous_rows(QuantizedGraph& graph) { const size_t vector_bytes = graph.is_quantized() ? ExDataMap::data_bytes(graph.padded_dim_, graph.quantization_bits_) @@ -70,7 +70,7 @@ struct QGConstructionTestAccess { } static QGBuilder from_graph( - QuantizedGraph& graph, + QuantizedGraph& graph, uint32_t ef, const float* data, const std::vector& offsets, @@ -81,7 +81,7 @@ struct QGConstructionTestAccess { builder.initialize_seed(data, offsets, neighbors); return builder; } - static auto codes(const QuantizedGraph& graph) { + static auto codes(const QuantizedGraph& graph) { std::vector result; for (PID id = 0; id < graph.num_points_; ++id) { const char* code = graph.is_quantized() @@ -110,35 +110,39 @@ struct QGConstructionTestAccess { } } static void search(QGBuilder& builder) { builder.search_new_neighbors(false); } - static auto encoded_neighbors(const QuantizedGraph& graph, PID id) { + static auto encoded_neighbors(const QuantizedGraph& graph, PID id) { return graph.get_neighbors(id); } static void set_encoded_neighbor( - QuantizedGraph& graph, PID source, size_t lane, PID target + QuantizedGraph& graph, PID source, size_t lane, PID target ) { graph.get_neighbors(source)[lane] = target; } - static void copy_vectors(QuantizedGraph& graph, const float* data, size_t threads) { + static void copy_vectors( + QuantizedGraph& graph, const float* data, size_t threads + ) { graph.copy_vectors(data, threads); } static void update( - QuantizedGraph& graph, PID source, const std::vector>& neighbors + QuantizedGraph& graph, + PID source, + const std::vector>& neighbors ) { graph.update_qg(source, neighbors); } - static void fill_batch_factors(QuantizedGraph& graph, PID source, float value) { + static void fill_batch_factors(QuantizedGraph& graph, PID source, float value) { QGBatchDataMap batch(graph.get_batch_data(source), graph.padded_dim_); std::fill_n(batch.f_add(), fastscan::kBatchSize, value); std::fill_n(batch.f_rescale(), fastscan::kBatchSize, value); } static std::pair batch_factors( - const QuantizedGraph& graph, PID source, size_t lane + const QuantizedGraph& graph, PID source, size_t lane ) { ConstQGBatchDataMap batch(graph.get_batch_data(source), graph.padded_dim_); return {batch.f_add()[lane], batch.f_rescale()[lane]}; } static std::array estimate( - const QuantizedGraph& graph, PID source, PID target + const QuantizedGraph& graph, PID source, PID target ) { std::vector query(graph.padded_dim_); graph.reconstruct_quantized_vector(source, query.data()); @@ -173,10 +177,32 @@ struct QGConstructionTestAccess { namespace { +TEST(QuantizedGraphCompatibilityTest, SupportsExplicitAndDeducedFloatTypes) { + using Graph = rabitqlib::symqg::QuantizedGraph; + static_assert(std::is_same_v>); + static_assert(!std::is_copy_constructible_v); + static_assert(std::is_nothrow_move_constructible_v); + static_assert(std::is_nothrow_move_assignable_v); + + Graph explicit_graph; + rabitqlib::symqg::QuantizedGraph deduced_graph; + rabitqlib::symqg::QuantizedGraph configured_graph(64, 64, 32); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + EXPECT_EQ(explicit_graph.num_vertices(), 0); + EXPECT_EQ(deduced_graph.num_vertices(), 0); + EXPECT_EQ(configured_graph.num_vertices(), 64); + Graph moved_graph(std::move(configured_graph)); + explicit_graph = std::move(moved_graph); + EXPECT_EQ(explicit_graph.dimension(), 64); +} + TEST(QuantizedGraphLayoutTest, KeepsVectorsCodesAndNeighborsInContiguousRows) { for (size_t bits : {0U, 4U, 8U}) { SCOPED_TRACE(bits); - QuantizedGraph graph(33, 65, 32, METRIC_L2, RotatorType::FhtKacRotator, bits); + QuantizedGraph graph( + 33, 65, 32, METRIC_L2, RotatorType::FhtKacRotator, bits + ); QGConstructionTestAccess::check_contiguous_rows(graph); } } @@ -187,7 +213,7 @@ static_assert(std::is_move_constructible_v>); static_assert( !std::is_constructible_v< QGBuilder, - QuantizedGraph&, + QuantizedGraph&, uint32_t, const float*, const std::vector&, @@ -285,7 +311,7 @@ TEST(QGConstructionTest, BuildsAndPrunesAfterInputReleaseUsingExistingCodes) { static_cast(1 + (i / kDim) % 4); } } - QuantizedGraph graph( + QuantizedGraph graph( kCount, kDim, 32, metric, RotatorType::FhtKacRotator, bits ); QGBuilder builder(graph, 64, data.data(), 1); @@ -334,7 +360,7 @@ TEST(QGConstructionTest, EncodedSeedSearchMatchesRetainedScores) { for (auto metric : {METRIC_L2, METRIC_IP}) { for (size_t bits : {0U, 4U, 8U}) { SCOPED_TRACE(::testing::Message() << metric << "/" << bits); - QuantizedGraph graph( + QuantizedGraph graph( kCount, kDim, kDegree, metric, RotatorType::FhtKacRotator, bits ); // Both builders search the same immutable encoded graph/rotation. @@ -374,7 +400,7 @@ TEST(QGConstructionTest, DefaultsToPipnnInitialization) { detail::build_initial_graph(data.data(), kCount, kDim, kDegree, metric, 1); for (size_t bits : {0U, 4U, 8U}) { SCOPED_TRACE(::testing::Message() << metric << "/" << bits); - QuantizedGraph graph( + QuantizedGraph graph( kCount, kDim, kDegree, metric, RotatorType::FhtKacRotator, bits ); QGBuilder builder(graph, 64, data.data(), 1); @@ -403,7 +429,7 @@ TEST(QGConstructionTest, SupportsExplicitRandomInitialization) { GTEST_SKIP() << "FastScan requires AVX2/FMA"; } std::vector data(65 * 65, 0.5F); - QuantizedGraph graph(65, 65, 32); + QuantizedGraph graph(65, 65, 32); QGBuilder builder(graph, 64, data.data(), 1, QGInitialization::Random); for (const auto& row : QGConstructionTestAccess::neighbors(builder)) { EXPECT_EQ(row.size(), 32U); @@ -420,7 +446,7 @@ TEST(QGConstructionTest, SupportsExplicitRandomInitialization) { } TEST(QGConstructionTest, RejectsNullDataForEveryInitializationMode) { - QuantizedGraph graph(33, 64, 32); + QuantizedGraph graph(33, 64, 32); EXPECT_THROW( (QGBuilder(graph, 32, nullptr, 1, QGInitialization::PiPNN)), std::invalid_argument ); @@ -437,8 +463,8 @@ TEST(QGConstructionTest, UsesExplicitThreadCountsWithoutChangingCallerState) { std::vector data(kCount * kDim, 0.25F); const int caller_threads = omp_get_max_threads(); - QuantizedGraph first(kCount, kDim, 32); - QuantizedGraph second(kCount, kDim, 32); + QuantizedGraph first(kCount, kDim, 32); + QuantizedGraph second(kCount, kDim, 32); QGBuilder first_builder(first, 32, data.data(), 1, QGInitialization::Random); EXPECT_EQ(omp_get_max_threads(), caller_threads); QGBuilder second_builder(second, 32, data.data(), 2, QGInitialization::PiPNN); @@ -458,7 +484,7 @@ TEST(QGConstructionTest, InitializesUnusedPartialBatchFactors) { for (size_t i = 0; i < data.size(); ++i) { data[i] = std::sin(static_cast(i) * 0.17F); } - QuantizedGraph graph(kCount, kDim, 32); + QuantizedGraph graph(kCount, kDim, 32); QGConstructionTestAccess::copy_vectors(graph, data.data(), 1); QGConstructionTestAccess::fill_batch_factors( graph, 0, std::numeric_limits::quiet_NaN() @@ -500,7 +526,7 @@ TEST(QGConstructionTest, RefinesPartialSeedOnceAfterReleasingInputs) { } offsets.push_back(edges.size()); } - QuantizedGraph graph( + QuantizedGraph graph( kCount, kDim, kDegree, metric, RotatorType::FhtKacRotator, bits ); auto builder = QGConstructionTestAccess::from_graph( @@ -560,7 +586,7 @@ TEST(QGConstructionTest, RefinesPartialSeedOnceAfterReleasingInputs) { } const std::string path = ::testing::TempDir() + "rabitq_qg_seeded.index"; graph.save(path.c_str()); - QuantizedGraph loaded; + QuantizedGraph loaded; loaded.load(path.c_str()); loaded.set_ef(96); loaded.search(query.data(), 10, loaded_ids.data(), loaded_distances.data()); @@ -575,7 +601,7 @@ TEST(QGConstructionTest, RefinesPartialSeedOnceAfterReleasingInputs) { TEST(QGConstructionTest, RejectsInvalidSeedStructureAndIds) { constexpr size_t kCount = 33, kDim = 64; std::vector data(kCount * kDim, 0.1F); - QuantizedGraph graph(kCount, kDim, 32); + QuantizedGraph graph(kCount, kDim, 32); const auto reject = [&](const std::vector& offsets, const std::vector& edges, const char* message) { @@ -608,31 +634,37 @@ TEST(QGConstructionTest, RejectsInvalidSeedStructureAndIds) { TEST(QuantizedGraphConfigurationTest, RejectsDegreeNotAlignedForFastScan) { EXPECT_THROW( - (QuantizedGraph(64, 64, 16, METRIC_L2, RotatorType::MatrixRotator)), + (QuantizedGraph(64, 64, 16, METRIC_L2, RotatorType::MatrixRotator)), std::invalid_argument ); } TEST(QuantizedGraphConfigurationTest, RejectsDegreeThatCannotExcludeSelf) { EXPECT_THROW( - (QuantizedGraph(32, 64, 32, METRIC_L2, RotatorType::MatrixRotator)), + (QuantizedGraph(32, 64, 32, METRIC_L2, RotatorType::MatrixRotator)), std::invalid_argument ); } TEST(QuantizedGraphConfigurationTest, AcceptsOnlySupportedVectorQuantizationBits) { - EXPECT_NO_THROW((QuantizedGraph(33, 64, 32, METRIC_L2, RotatorType::MatrixRotator, 0))); - EXPECT_NO_THROW((QuantizedGraph(33, 64, 32, METRIC_L2, RotatorType::MatrixRotator, 4))); - EXPECT_NO_THROW((QuantizedGraph(33, 64, 32, METRIC_L2, RotatorType::MatrixRotator, 8))); + EXPECT_NO_THROW( + (QuantizedGraph(33, 64, 32, METRIC_L2, RotatorType::MatrixRotator, 0)) + ); + EXPECT_NO_THROW( + (QuantizedGraph(33, 64, 32, METRIC_L2, RotatorType::MatrixRotator, 4)) + ); + EXPECT_NO_THROW( + (QuantizedGraph(33, 64, 32, METRIC_L2, RotatorType::MatrixRotator, 8)) + ); EXPECT_THROW( - (QuantizedGraph(33, 64, 32, METRIC_L2, RotatorType::MatrixRotator, 6)), + (QuantizedGraph(33, 64, 32, METRIC_L2, RotatorType::MatrixRotator, 6)), std::invalid_argument ); } TEST(QuantizedGraphConfigurationTest, RejectsUnsupportedMetric) { EXPECT_THROW( - (QuantizedGraph( + (QuantizedGraph( 33, 64, 32, static_cast(255), RotatorType::MatrixRotator )), std::invalid_argument @@ -641,13 +673,13 @@ TEST(QuantizedGraphConfigurationTest, RejectsUnsupportedMetric) { TEST(QuantizedGraphConfigurationTest, RejectsZeroDimension) { EXPECT_THROW( - (QuantizedGraph(33, 0, 32, METRIC_L2, RotatorType::MatrixRotator)), + (QuantizedGraph(33, 0, 32, METRIC_L2, RotatorType::MatrixRotator)), std::invalid_argument ); } TEST(QuantizedGraphConfigurationTest, RejectsOutOfRangeEntryPoint) { - QuantizedGraph graph(33, 64, 32); + QuantizedGraph graph(33, 64, 32); EXPECT_NO_THROW(graph.set_ep(32)); EXPECT_THROW(graph.set_ep(33), std::invalid_argument); EXPECT_EQ(graph.entry_point(), 32U); @@ -658,14 +690,14 @@ TEST(QuantizedGraphPersistenceTest, RejectsMalformedPayloadWithoutChangingTarget constexpr size_t kDim = 64; constexpr size_t kDegree = 32; std::vector data(kNumPoints * kDim, 0.25F); - QuantizedGraph source( + QuantizedGraph source( kNumPoints, kDim, kDegree, METRIC_L2, RotatorType::MatrixRotator, 4 ); QGBuilder builder(source, kDegree, data.data(), 1); builder.build(2); const std::string path = ::testing::TempDir() + "rabitq_qg_malformed.index"; - QuantizedGraph target(65, kDim, kDegree, METRIC_IP); + QuantizedGraph target(65, kDim, kDegree, METRIC_IP); source.save(path.c_str()); std::filesystem::resize_file(path, std::filesystem::file_size(path) - 1); @@ -720,7 +752,7 @@ TEST(QuantizedGraphPersistenceTest, RejectsMalformedPayloadWithoutChangingTarget } TEST(QuantizedGraphLifecycleTest, DestroysConcreteRotatorThroughBasePointer) { - QuantizedGraph graph(33, 64, 32, METRIC_L2, RotatorType::MatrixRotator); + QuantizedGraph graph(33, 64, 32, METRIC_L2, RotatorType::MatrixRotator); EXPECT_EQ(graph.num_vertices(), 33U); } @@ -731,13 +763,13 @@ TEST(QuantizedGraphLifecycleTest, RejectsSearchAndSaveBeforeBuild) { std::array ids{}; std::array distances{}; - QuantizedGraph empty; + QuantizedGraph empty; EXPECT_THROW(empty.save(path.c_str()), std::logic_error); EXPECT_THROW( empty.search(query.data(), 1, ids.data(), distances.data()), std::logic_error ); - QuantizedGraph configured(33, 64, 32); + QuantizedGraph configured(33, 64, 32); configured.set_ef(1); EXPECT_THROW(configured.save(path.c_str()), std::logic_error); EXPECT_THROW( @@ -763,7 +795,7 @@ TEST(QuantizedGraphLifecycleTest, BuilderDoesNotPublishPartialGraph) { SCOPED_TRACE(static_cast(init)); const std::string path = ::testing::TempDir() + "rabitq_qg_partial.index"; std::remove(path.c_str()); - QuantizedGraph graph(kNumPoints, kDim, kDegree); + QuantizedGraph graph(kNumPoints, kDim, kDegree); QGBuilder builder(graph, kDegree, data.data(), 1, init); graph.set_ef(1); EXPECT_THROW(graph.save(path.c_str()), std::logic_error); @@ -793,7 +825,9 @@ TEST(QGBuilderMetricTest, UsesInnerProductDistanceToChooseEntryPoint) { ASSERT_EQ(expected, 0U); ASSERT_NE(euclidean_entry, expected); - QuantizedGraph graph(kNumPoints, kDim, kDegree, METRIC_IP, RotatorType::MatrixRotator); + QuantizedGraph graph( + kNumPoints, kDim, kDegree, METRIC_IP, RotatorType::MatrixRotator + ); QGBuilder builder(graph, kDegree, data.data(), 1); EXPECT_EQ(graph.entry_point(), expected); @@ -848,7 +882,7 @@ TEST(QGQuantTest, SearchesAndRoundTripsFourAndEightBitIndexes) { for (size_t bits : {4U, 8U}) { SCOPED_TRACE(bits); - QuantizedGraph graph( + QuantizedGraph graph( kNumPoints, kDim, kDegree, METRIC_L2, RotatorType::MatrixRotator, bits ); { @@ -868,7 +902,7 @@ TEST(QGQuantTest, SearchesAndRoundTripsFourAndEightBitIndexes) { const std::string path = ::testing::TempDir() + "rabitq_qg_quant_" + std::to_string(bits) + ".index"; graph.save(path.c_str()); - QuantizedGraph loaded; + QuantizedGraph loaded; loaded.set_ef(kNumPoints); loaded.load(path.c_str()); EXPECT_TRUE(loaded.is_quantized()); @@ -894,7 +928,7 @@ TEST(QGSearchTest, RejectsInvalidKAndEfInsteadOfReturningPartialResults) { for (size_t i = 0; i < data.size(); ++i) { data[i] = std::sin(static_cast(i) * 0.11F); } - QuantizedGraph graph(kNumPoints, kDim, kDegree); + QuantizedGraph graph(kNumPoints, kDim, kDegree); QGBuilder builder(graph, kDegree, data.data(), 1, QGInitialization::Random); builder.build(2); @@ -963,7 +997,7 @@ TEST(QGSearchTest, ParallelQueriesMatchSerialAcrossIndexesAndSettings) { for (size_t i = 0; i < queries.size(); ++i) { queries[i] = std::cos(static_cast(i) * 0.07F); } - QuantizedGraph graph( + QuantizedGraph graph( num_points, dim, 32, metric, RotatorType::FhtKacRotator, bits ); {