| 15 | namespace rabitqlib::python_bindings { |
| 16 | |
| 17 | class SymqgIndex { |
| 18 | public: |
| 19 | SymqgIndex(size_t dim, size_t max_degree, const std::string& metric = "l2") |
| 20 | : dim_(dim) |
| 21 | , max_degree_(max_degree) |
| 22 | , metric_(metric_from_string(metric)) {} |
| 23 | |
| 24 | void build(py::handle data, size_t ef_construction, size_t num_threads = 1) { |
| 25 | auto data_array = ensure_2d_array<float>(data, "data"); |
| 26 | if (static_cast<size_t>(data_array.shape(1)) != dim_) { |
| 27 | throw std::invalid_argument("data dimension does not match index dim"); |
| 28 | } |
| 29 | |
| 30 | num_points_ = static_cast<size_t>(data_array.shape(0)); |
| 31 | index_ = std::make_unique<rabitqlib::symqg::QuantizedGraph<float>>( |
| 32 | num_points_, dim_, max_degree_, metric_, rabitqlib::RotatorType::FhtKacRotator |
| 33 | ); |
| 34 | |
| 35 | rabitqlib::symqg::QGBuilder builder(*index_, ef_construction, data_array.data(), num_threads); |
| 36 | builder.build(); |
| 37 | built_ = true; |
| 38 | } |
| 39 | |
| 40 | py::tuple search(py::handle queries, size_t k, size_t ef, size_t num_threads = 1) { |
| 41 | auto query_array = ensure_2d_array<float>(queries, "queries"); |
| 42 | if (!built_) { |
| 43 | throw std::runtime_error("SymqgIndex must be built or loaded before search"); |
| 44 | } |
| 45 | if (static_cast<size_t>(query_array.shape(1)) != dim_) { |
| 46 | throw std::invalid_argument("query dimension does not match index dim"); |
| 47 | } |
| 48 | |
| 49 | index_->set_ef(ef); |
| 50 | |
| 51 | const size_t nq = static_cast<size_t>(query_array.shape(0)); |
| 52 | const auto shape = std::vector<ssize_t>{static_cast<ssize_t>(nq), static_cast<ssize_t>(k)}; |
| 53 | auto ids = py::array_t<rabitqlib::PID>(shape); |
| 54 | auto dists = py::array_t<float>(shape); |
| 55 | auto ids_buf = ids.mutable_unchecked<2>(); |
| 56 | auto dists_buf = dists.mutable_unchecked<2>(); |
| 57 | |
| 58 | rabitqlib::ivf::parallel_for( |
| 59 | 0, |
| 60 | nq, |
| 61 | num_threads, |
| 62 | [&](size_t idx, size_t /*threadId*/) { |
| 63 | std::vector<rabitqlib::PID> row_ids(k, 0); |
| 64 | std::vector<float> row_dists(k, 0.0F); |
| 65 | index_->search(query_array.data() + (idx * dim_), static_cast<uint32_t>(k), row_ids.data(), row_dists.data()); |
| 66 | for (size_t j = 0; j < k; ++j) { |
| 67 | ids_buf(static_cast<ssize_t>(idx), static_cast<ssize_t>(j)) = row_ids[j]; |
| 68 | dists_buf(static_cast<ssize_t>(idx), static_cast<ssize_t>(j)) = row_dists[j]; |
| 69 | } |
| 70 | } |
| 71 | ); |
| 72 | |
| 73 | return py::make_tuple(ids, dists); |
| 74 | } |