Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions examples/cpp/115_index_brute_force_hamming.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// Copyright 2024-present the vsag project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <vsag/vsag.h>

#include <cstdlib>
#include <cstring>
#include <iostream>
#include <random>
#include <vector>

// Example: Hamming distance over bit-packed binary vectors with the brute_force index.
//
// For binary vectors, "dim" is the number of logical bits. Each vector is stored as a
// contiguous byte array of ceil(dim / 8) bytes. Bits are packed least-significant-bit
// first inside each byte, and the unused high bits of the final byte are ignored by the
// Hamming distance computation:
//
// hamming(a, b) = popcount(a XOR b)

int
main(int argc, char** argv) {
vsag::init();

/******************* Prepare Base Dataset *****************/
int64_t num_vectors = 1000;
int64_t dim = 256; // number of logical bits
int64_t bytes_per_vector = (dim + 7) / 8;

std::vector<int64_t> ids(num_vectors);
std::vector<uint8_t> datas(num_vectors * bytes_per_vector);
std::mt19937 rng(47);
std::uniform_int_distribution<int> distrib_byte(0, 255);
for (int64_t i = 0; i < num_vectors; ++i) {
ids[i] = i;
}
for (auto& byte : datas) {
byte = static_cast<uint8_t>(distrib_byte(rng));
}

auto base = vsag::Dataset::Make();
base->NumElements(num_vectors)
->Dim(dim)
->Ids(ids.data())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[critical] Calls a public Dataset::BinaryVectors API that does not exist.

include/vsag/dataset.h does not declare a BinaryVectors accessor, so this example cannot compile. Add the public binary-vector dataset interface or remove the example until the interface exists.

->BinaryVectors(datas.data())
->Owner(false);

/******************* Create BruteForce Index *****************/
std::string index_parameters = R"(
{
"dtype": "binary",
"metric_type": "hamming",
Comment on lines +62 to +63

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Accept binary/Hamming parameters in the public index factory

Any Factory::CreateIndex call using this new configuration is rejected before reaching the binary implementation. Engine::CreateIndex calls IndexCommonParam::CheckAndCreate, but fill_datatype and fill_metrictype in src/index_common_param.cpp still reject "binary" and "hamming", respectively. Consequently, the example and all newly added public-API tests receive INVALID_ARGUMENT instead of an index. Add the new dtype and metric to common-parameter parsing alongside this feature.

Useful? React with 👍 / 👎.

"dim": 256
}
)";
auto index = vsag::Factory::CreateIndex("brute_force", index_parameters).value();

/******************* Build BruteForce Index *****************/
if (auto build_result = index->Build(base); build_result.has_value()) {
std::cout << "After Build(), Index BruteForce contains: " << index->GetNumElements()
<< std::endl;
} else {
std::cerr << "Failed to build index: " << build_result.error().message << std::endl;
return -1;
}

/******************* Prepare Query Dataset *****************/
// Query with a copy of the last base vector flipped in two bits, so the exact
// Hamming distance to that vector must be 2.
std::vector<uint8_t> query_vector(bytes_per_vector);
std::memcpy(
query_vector.data(), datas.data() + (num_vectors - 1) * bytes_per_vector, bytes_per_vector);
query_vector[0] ^= 0x05; // flip two bits

auto query = vsag::Dataset::Make();
query->NumElements(1)->Dim(dim)->BinaryVectors(query_vector.data())->Owner(false);

/******************* KnnSearch For BruteForce Index *****************/
int64_t topk = 10;
auto result = index->KnnSearch(query, topk, R"({})").value();

/******************* Verify and Print Search Result *****************/
auto popcount = [](const uint8_t* x, const uint8_t* y, int64_t num_bytes) {
uint64_t distance = 0;
for (int64_t i = 0; i < num_bytes; ++i) {
uint8_t diff = static_cast<uint8_t>(x[i] ^ y[i]);
while (diff != 0) {
diff = static_cast<uint8_t>(diff & (diff - 1));
++distance;
}
}
return distance;
};

std::cout << "results (id: distance):" << std::endl;
for (int64_t i = 0; i < result->GetDim(); ++i) {
int64_t id = result->GetIds()[i];
auto expected =
popcount(query_vector.data(), datas.data() + id * bytes_per_vector, bytes_per_vector);
if (static_cast<uint64_t>(result->GetDistances()[i]) != expected) {
std::cerr << "distance mismatch for id " << id << std::endl;
return -1;
}
std::cout << id << ": " << result->GetDistances()[i] << std::endl;
}

if (result->GetIds()[0] != num_vectors - 1 || result->GetDistances()[0] != 2.0F) {
std::cerr << "nearest neighbor verification failed" << std::endl;
return -1;
}
std::cout << "verified: nearest id " << result->GetIds()[0] << " has Hamming distance "
<< result->GetDistances()[0] << std::endl;

return 0;
}
3 changes: 3 additions & 0 deletions include/vsag/constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ extern const char* const FLOAT32_VECTORS;
extern const char* const FLOAT16_VECTORS;
extern const char* const SPARSE_VECTORS;
extern const char* const INT8_VECTORS;
extern const char* const BINARY_VECTORS;
extern const char* const ATTRIBUTE_SETS;
extern const char* const DATASET_PATHS;
extern const char* const EXTRA_INFOS;
Expand All @@ -53,10 +54,12 @@ extern const char* const SIMPLEFLAT_IDS;
extern const char* const METRIC_L2;
extern const char* const METRIC_COSINE;
extern const char* const METRIC_IP;
extern const char* const METRIC_HAMMING;
extern const char* const DATATYPE_FLOAT32;
extern const char* const DATATYPE_FLOAT16;
extern const char* const DATATYPE_BFLOAT16;
extern const char* const DATATYPE_INT8;
extern const char* const DATATYPE_BINARY;
extern const char* const DATATYPE_SPARSE;
extern const char* const BLANK_INDEX;

Expand Down
19 changes: 19 additions & 0 deletions include/vsag/dataset.h
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,25 @@ class Dataset : public std::enable_shared_from_this<Dataset> {
virtual const int8_t*
GetInt8Vectors() const = 0;

/**
* @brief Sets the bit-packed binary vector array for the dataset.
*
* Each vector occupies ceil(Dim() / 8) bytes. Unused high bits in the final byte are ignored.
*
* @param vectors Pointer to the contiguous array of packed binary vectors.
* @return DatasetPtr A shared pointer to the dataset with updated binary vectors.
*/
virtual DatasetPtr
BinaryVectors(const uint8_t* vectors) = 0;

/**
* @brief Retrieves the bit-packed binary vector array of the dataset.
*
* @return const uint8_t* Pointer to the array of packed binary vectors.
*/
virtual const uint8_t*
GetBinaryVectors() const = 0;

/**
* @brief Sets the float32 vector array for the dataset.
*
Expand Down
Loading