| 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 | } |
| 75 | |
| 76 | void save(const std::string& path) const { |
| 77 | if (!built_) { |
nothing calls this directly
no test coverage detected