usearch 0.18.8 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/python/lib.cpp CHANGED
@@ -13,8 +13,9 @@
13
13
  * @copyright Copyright (c) 2023
14
14
  */
15
15
  #define PY_SSIZE_T_CLEAN
16
- #define NOMINMAX // Some of our dependencies call `std::max(x, y)`, which crashes Windows builds
17
- #include <thread>
16
+ #define NOMINMAX // Some of our dependencies call `std::max(x, y)`, which crashes Windows builds
17
+ #include <limits> // `std::numeric_limits`
18
+ #include <thread> // `std::thread`
18
19
 
19
20
  #include <pybind11/numpy.h>
20
21
  #include <pybind11/pybind11.h>
@@ -24,12 +25,23 @@
24
25
  using namespace unum::usearch;
25
26
  using namespace unum;
26
27
 
28
+ /**
29
+ * @brief The signature of the user-defined function.
30
+ * Can be just two array pointers, precompiled for a specific array length,
31
+ * or include one or two array sizes as 64-bit unsigned integers.
32
+ */
33
+ enum class metric_signature_t {
34
+ array_array_k = 0,
35
+ array_array_size_k,
36
+ array_size_array_size_k,
37
+ };
38
+
27
39
  namespace py = pybind11;
28
40
  using py_shape_t = py::array::ShapeContainer;
29
41
 
30
42
  using label_t = std::uint32_t;
31
43
  using distance_t = punned_distance_t;
32
- using metric_t = punned_metric_t;
44
+ using metric_t = index_punned_dense_metric_t;
33
45
  using id_t = std::uint32_t;
34
46
  using big_id_t = std::uint64_t;
35
47
 
@@ -50,7 +62,7 @@ struct punned_index_py_t : public punned_index_t {
50
62
 
51
63
  using set_member_t = std::uint32_t;
52
64
  using set_view_t = span_gt<set_member_t const>;
53
- using sparse_index_t = index_gt<jaccard_gt<set_member_t>, label_t, id_t, set_member_t>;
65
+ using sparse_index_t = index_gt<jaccard_gt<set_member_t>, label_t, id_t>;
54
66
 
55
67
  struct sparse_index_py_t : public sparse_index_t {
56
68
  using native_t = sparse_index_t;
@@ -63,32 +75,58 @@ struct sparse_index_py_t : public sparse_index_t {
63
75
  sparse_index_py_t(native_t&& base) : native_t(std::move(base)) {}
64
76
  };
65
77
 
66
- template <typename scalar_at> metric_t udf(std::size_t metric_uintptr) {
67
- return [metric_uintptr](punned_vector_view_t a, punned_vector_view_t b) -> distance_t {
68
- using metric_raw_t = punned_distance_t (*)(scalar_at const*, scalar_at const*);
69
- metric_raw_t metric_ptr = reinterpret_cast<metric_raw_t>(metric_uintptr);
70
- return metric_ptr((scalar_at const*)a.data(), (scalar_at const*)b.data());
71
- };
78
+ template <typename scalar_at>
79
+ metric_t udf(metric_kind_t kind, metric_signature_t signature, std::uintptr_t metric_uintptr) {
80
+ //
81
+ metric_t result;
82
+ result.kind_ = kind;
83
+ switch (signature) {
84
+ case metric_signature_t::array_array_k:
85
+ result.func_ = [metric_uintptr](punned_vector_view_t a, punned_vector_view_t b) -> distance_t {
86
+ using metric_raw_t = punned_distance_t (*)(scalar_at const*, scalar_at const*);
87
+ metric_raw_t metric_ptr = reinterpret_cast<metric_raw_t>(metric_uintptr);
88
+ return metric_ptr((scalar_at const*)a.data(), (scalar_at const*)b.data());
89
+ };
90
+ break;
91
+ case metric_signature_t::array_array_size_k:
92
+ result.func_ = [metric_uintptr](punned_vector_view_t a, punned_vector_view_t b) -> distance_t {
93
+ using metric_raw_t = punned_distance_t (*)(scalar_at const*, scalar_at const*, size_t);
94
+ metric_raw_t metric_ptr = reinterpret_cast<metric_raw_t>(metric_uintptr);
95
+ return metric_ptr((scalar_at const*)a.data(), (scalar_at const*)b.data(), a.size() / sizeof(scalar_at));
96
+ };
97
+ break;
98
+ case metric_signature_t::array_size_array_size_k:
99
+ result.func_ = [metric_uintptr](punned_vector_view_t a, punned_vector_view_t b) -> distance_t {
100
+ using metric_raw_t = punned_distance_t (*)(scalar_at const*, size_t, scalar_at const*, size_t);
101
+ metric_raw_t metric_ptr = reinterpret_cast<metric_raw_t>(metric_uintptr);
102
+ return metric_ptr( //
103
+ (scalar_at const*)a.data(), a.size() / sizeof(scalar_at), //
104
+ (scalar_at const*)b.data(), b.size() / sizeof(scalar_at));
105
+ };
106
+ break;
107
+ }
108
+ return result;
72
109
  }
73
110
 
74
- metric_t udf(std::size_t metric_uintptr, scalar_kind_t accuracy) {
111
+ metric_t udf(metric_kind_t kind, metric_signature_t signature, std::uintptr_t metric_uintptr, scalar_kind_t accuracy) {
75
112
  switch (accuracy) {
76
- case scalar_kind_t::f8_k: return udf<f8_bits_t>(metric_uintptr);
77
- case scalar_kind_t::f16_k: return udf<f16_t>(metric_uintptr);
78
- case scalar_kind_t::f32_k: return udf<f32_t>(metric_uintptr);
79
- case scalar_kind_t::f64_k: return udf<f64_t>(metric_uintptr);
113
+ case scalar_kind_t::f8_k: return udf<f8_bits_t>(kind, signature, metric_uintptr);
114
+ case scalar_kind_t::f16_k: return udf<f16_t>(kind, signature, metric_uintptr);
115
+ case scalar_kind_t::f32_k: return udf<f32_t>(kind, signature, metric_uintptr);
116
+ case scalar_kind_t::f64_k: return udf<f64_t>(kind, signature, metric_uintptr);
80
117
  default: return {};
81
118
  }
82
119
  }
83
120
 
84
- static punned_index_py_t make_index( //
85
- std::size_t dimensions, //
86
- scalar_kind_t scalar_kind, //
87
- metric_kind_t metric_kind, //
88
- std::size_t connectivity, //
89
- std::size_t expansion_add, //
90
- std::size_t expansion_search, //
91
- std::size_t metric_uintptr, //
121
+ static punned_index_py_t make_index( //
122
+ std::size_t dimensions, //
123
+ scalar_kind_t scalar_kind, //
124
+ metric_kind_t metric_kind, //
125
+ std::size_t connectivity, //
126
+ std::size_t expansion_add, //
127
+ std::size_t expansion_search, //
128
+ metric_signature_t metric_signature, //
129
+ std::uintptr_t metric_uintptr, //
92
130
  bool tune) {
93
131
 
94
132
  index_config_t config;
@@ -99,7 +137,8 @@ static punned_index_py_t make_index( //
99
137
 
100
138
  if (metric_uintptr)
101
139
  return punned_index_t::make( //
102
- dimensions, udf(metric_uintptr, scalar_kind), config, scalar_kind, expansion_add, expansion_search);
140
+ dimensions, udf(metric_kind, metric_signature, metric_uintptr, scalar_kind), config, scalar_kind,
141
+ expansion_add, expansion_search);
103
142
  else
104
143
  return punned_index_t::make(dimensions, metric_kind, config, scalar_kind, expansion_add, expansion_search);
105
144
  }
@@ -417,14 +456,18 @@ template <typename index_at> py::object get_member(index_at const& index, label_
417
456
  throw std::invalid_argument("Incompatible scalars in the query matrix!");
418
457
  }
419
458
 
420
- template <typename index_at> py::array_t<label_t> get_labels(index_at const& index) {
421
- std::size_t result_length = index.size();
422
- py::array_t<label_t> result_py(static_cast<Py_ssize_t>(result_length));
459
+ template <typename index_at> py::array_t<label_t> get_labels(index_at const& index, std::size_t limit) {
460
+ limit = std::min(index.size(), limit);
461
+ py::array_t<label_t> result_py(static_cast<Py_ssize_t>(limit));
423
462
  auto result_py1d = result_py.template mutable_unchecked<1>();
424
- index.export_labels(&result_py1d(0), result_length);
463
+ index.export_labels(&result_py1d(0), limit);
425
464
  return result_py;
426
465
  }
427
466
 
467
+ template <typename index_at> py::array_t<label_t> get_all_labels(index_at const& index) {
468
+ return get_labels(index, index.size());
469
+ }
470
+
428
471
  template <typename element_at> bool has_duplicates(element_at const* begin, element_at const* end) {
429
472
  if (begin == end)
430
473
  return false;
@@ -539,6 +582,11 @@ PYBIND11_MODULE(compiled, m) {
539
582
  m.attr("USES_SIMSIMD") = py::int_(USEARCH_USE_SIMSIMD);
540
583
  m.attr("USES_NATIVE_F16") = py::int_(USEARCH_USE_NATIVE_F16);
541
584
 
585
+ py::enum_<metric_signature_t>(m, "MetricSignature")
586
+ .value("ArrayArray", metric_signature_t::array_array_k)
587
+ .value("ArrayArraySize", metric_signature_t::array_array_size_k)
588
+ .value("ArraySizeArraySize", metric_signature_t::array_size_array_size_k);
589
+
542
590
  py::enum_<metric_kind_t>(m, "MetricKind")
543
591
  .value("Unknown", metric_kind_t::unknown_k)
544
592
  .value("IP", metric_kind_t::ip_k)
@@ -558,18 +606,44 @@ PYBIND11_MODULE(compiled, m) {
558
606
  .value("F8", scalar_kind_t::f8_k)
559
607
  .value("B1", scalar_kind_t::b1x8_k);
560
608
 
609
+ auto h = py::class_<file_head_result_t>(m, "IndexMetadata");
610
+ h.def(py::init([](std::string const& path) {
611
+ file_head_result_t h = index_metadata(path.c_str());
612
+ h.error.raise();
613
+ return h;
614
+ }));
615
+ h.def_property_readonly("version", [](file_head_result_t const& h) {
616
+ return //
617
+ std::to_string(h.version_major) + "." + //
618
+ std::to_string(h.version_minor) + "." + //
619
+ std::to_string(h.version_patch);
620
+ });
621
+ h.def_readonly("metric", &file_head_result_t::metric);
622
+ h.def_readonly("connectivity", &file_head_result_t::connectivity);
623
+ h.def_readonly("max_level", &file_head_result_t::max_level);
624
+ h.def_readonly("vector_alignment", &file_head_result_t::vector_alignment);
625
+ h.def_readonly("bytes_per_label", &file_head_result_t::bytes_per_label);
626
+ h.def_readonly("bytes_per_id", &file_head_result_t::bytes_per_id);
627
+ h.def_readonly("scalar_kind", &file_head_result_t::scalar_kind);
628
+ h.def_readonly("size", &file_head_result_t::size);
629
+ h.def_readonly("entry_idx", &file_head_result_t::entry_idx);
630
+ h.def_readonly("bytes_for_graphs", &file_head_result_t::bytes_for_graphs);
631
+ h.def_readonly("bytes_for_vectors", &file_head_result_t::bytes_for_vectors);
632
+ h.def_readonly("bytes_checksum", &file_head_result_t::bytes_checksum);
633
+
561
634
  auto i = py::class_<punned_index_py_t>(m, "Index");
562
635
 
563
- i.def(py::init(&make_index), //
564
- py::kw_only(), //
565
- py::arg("ndim") = 0, //
566
- py::arg("dtype") = scalar_kind_t::f32_k, //
567
- py::arg("metric") = metric_kind_t::ip_k, //
568
- py::arg("connectivity") = default_connectivity(), //
569
- py::arg("expansion_add") = default_expansion_add(), //
570
- py::arg("expansion_search") = default_expansion_search(), //
571
- py::arg("metric_pointer") = 0, //
572
- py::arg("tune") = false //
636
+ i.def(py::init(&make_index), //
637
+ py::kw_only(), //
638
+ py::arg("ndim") = 0, //
639
+ py::arg("dtype") = scalar_kind_t::f32_k, //
640
+ py::arg("metric") = metric_kind_t::ip_k, //
641
+ py::arg("connectivity") = default_connectivity(), //
642
+ py::arg("expansion_add") = default_expansion_add(), //
643
+ py::arg("expansion_search") = default_expansion_search(), //
644
+ py::arg("metric_signature") = metric_signature_t::array_array_k, //
645
+ py::arg("metric_pointer") = 0, //
646
+ py::arg("tune") = false //
573
647
  );
574
648
 
575
649
  i.def( //
@@ -604,7 +678,8 @@ PYBIND11_MODULE(compiled, m) {
604
678
  i.def_property_readonly("connectivity", &punned_index_py_t::connectivity);
605
679
  i.def_property_readonly("capacity", &punned_index_py_t::capacity);
606
680
  i.def_property_readonly( //
607
- "dtype", [](punned_index_py_t const& index) -> std::string { return scalar_kind_name(index.scalar_kind()); });
681
+ "dtype",
682
+ [](punned_index_py_t const& index) -> std::string { return scalar_kind_name(index.metric().scalar_kind_); });
608
683
  i.def_property_readonly( //
609
684
  "memory_usage", [](punned_index_py_t const& index) -> std::size_t { return index.memory_usage(); });
610
685
 
@@ -612,7 +687,8 @@ PYBIND11_MODULE(compiled, m) {
612
687
  i.def_property("expansion_search", &punned_index_py_t::expansion_search,
613
688
  &punned_index_py_t::change_expansion_search);
614
689
 
615
- i.def_property_readonly("labels", &get_labels<punned_index_py_t>);
690
+ i.def_property_readonly("labels", &get_all_labels<punned_index_py_t>);
691
+ i.def("get_labels", &get_labels<punned_index_py_t>, py::arg("limit") = std::numeric_limits<std::size_t>::max());
616
692
  i.def("__contains__", &punned_index_py_t::contains);
617
693
  i.def( //
618
694
  "__getitem__", &get_member<punned_index_py_t>, py::arg("label"), py::arg("dtype") = scalar_kind_t::f32_k);