usearch 2.11.0 → 2.11.1

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/README.md CHANGED
@@ -94,9 +94,10 @@ USearch is compact and broadly compatible without sacrificing performance, prima
94
94
  | Supported metrics ² | 9 fixed metrics | any metric | extendible |
95
95
  | Supported languages ³ | C++, Python | 10 languages | portable |
96
96
  | Supported ID types ⁴ | 32-bit, 64-bit | 32-bit, 40-bit, 64-bit | efficient |
97
- | Required dependencies ⁵ | BLAS, OpenMP | - | light-weight |
98
- | Bindings ⁶ | SWIG | Native | low-latency |
99
- | Python binding size ⁷ | [~ 10 MB][faiss-weight] | [< 1 MB][usearch-weight] | deployable |
97
+ | Filtering ⁵ | ban-lists | any predicates | composable |
98
+ | Required dependencies ⁶ | BLAS, OpenMP | - | light-weight |
99
+ | Bindings ⁷ | SWIG | Native | low-latency |
100
+ | Python binding size ⁸ | [~ 10 MB][faiss-weight] | [< 1 MB][usearch-weight] | deployable |
100
101
 
101
102
  [sloc]: https://en.wikipedia.org/wiki/Source_lines_of_code
102
103
  [faiss-weight]: https://pypi.org/project/faiss-cpu/#files
@@ -107,9 +108,10 @@ USearch is compact and broadly compatible without sacrificing performance, prima
107
108
  > ² User-defined metrics allow you to customize your search for various applications, from GIS to creating custom metrics for composite embeddings from multiple AI models or hybrid full-text and semantic search.
108
109
  > ³ With USearch, you can reuse the same preconstructed index in various programming languages.
109
110
  > ⁴ The 40-bit integer allows you to store 4B+ vectors without allocating 8 bytes for every neighbor reference in the proximity graph.
110
- > ⁵ Lack of obligatory dependencies makes USearch much more portable.
111
- > ⁶ Native bindings introduce lower call latencies than more straightforward approaches.
112
- > ⁷ Lighter bindings make downloads and deployments faster.
111
+ > ⁵ With USearch the index can be combined with arbitrary external containers, like Bloom filters or third-party databases, to filter out irrelevant keys during index traversal.
112
+ > ⁶ Lack of obligatory dependencies makes USearch much more portable.
113
+ > ⁷ Native bindings introduce lower call latencies than more straightforward approaches.
114
+ > ⁸ Lighter bindings make downloads and deployments faster.
113
115
 
114
116
  [intel-benchmarks]: https://www.unum.cloud/blog/2023-11-07-scaling-vector-search-with-intel
115
117
 
@@ -192,7 +194,7 @@ When compared to FAISS's `IndexFlatL2` in Google Colab, __[USearch may offer up
192
194
  - `faiss.IndexFlatL2`: __55.3 ms__.
193
195
  - `usearch.index.search`: __2.54 ms__.
194
196
 
195
- ## User-Defined Functions
197
+ ## User-Defined Metrics
196
198
 
197
199
  While most vector search packages concentrate on just two metrics, "Inner Product distance" and "Euclidean distance", USearch allows arbitrary user-defined metrics.
198
200
  This flexibility allows you to customize your search for various applications, from computing geospatial coordinates with the rare [Haversine][haversine] distance to creating custom metrics for composite embeddings from multiple AI models, like joint image-text embeddings.
@@ -228,6 +230,23 @@ So you can apply it in [obscure][obscure] applications, like searching for simil
228
230
  [cppyy]: https://cppyy.readthedocs.io/en/latest/
229
231
  [peachpy]: https://github.com/Maratyszcza/PeachPy
230
232
 
233
+ ## Filtering and Predicate Functions
234
+
235
+ Sometimes you may want to cross-reference search-results against some external database or filter them based on some criteria.
236
+ In most engines, you'd have to manually perform paging requests, successively filtering the results.
237
+ In USearch you can simply pass a predicate function to the search method, which will be applied directly during graph traversal.
238
+ In Rust that would look like this:
239
+
240
+ ```rust
241
+ let is_odd = |key: Key| key % 2 == 1;
242
+ let query = vec![0.2, 0.1, 0.2, 0.1, 0.3];
243
+ let results = index.filtered_search(&query, 10, is_odd).unwrap();
244
+ assert!(
245
+ results.keys.iter().all(|&key| key % 2 == 1),
246
+ "All keys must be odd"
247
+ );
248
+ ```
249
+
231
250
  ## Memory Efficiency, Downcasting, and Quantization
232
251
 
233
252
  Training a quantization model and dimension-reduction is a common approach to accelerate vector search.
@@ -452,7 +471,7 @@ doi = {10.5281/zenodo.7949416},
452
471
  author = {Vardanian, Ash},
453
472
  title = {{USearch by Unum Cloud}},
454
473
  url = {https://github.com/unum-cloud/usearch},
455
- version = {2.11.0},
474
+ version = {2.11.1},
456
475
  year = {2023},
457
476
  month = oct,
458
477
  }
package/c/README.md CHANGED
@@ -42,7 +42,7 @@ int main() {
42
42
 
43
43
  // Search:
44
44
  usearch_key_t found_keys[10];
45
- float found_distances[10];
45
+ usearch_distance_t found_distances[10];
46
46
  size_t found_count = usearch_search(
47
47
  index, &vector[0], usearch_scalar_f32_k, 10,
48
48
  &found_keys[0], &found_distances[0], &error);
@@ -113,7 +113,8 @@ simsimd_distance_t callback(void const* a, void const* b, void* state) {
113
113
  // Your custom metric implementation here
114
114
  }
115
115
 
116
- usearch_change_metric(index, callback, NULL, usearch_metric_unknown_k, &error);
116
+ void callback_state = NULL;
117
+ usearch_change_metric(index, callback, callback_state, usearch_metric_unknown_k, &error);
117
118
  ```
118
119
 
119
120
  You can always revert back to one of the native metrics by calling:
@@ -133,8 +134,10 @@ int is_odd(usearch_key_t key, void* state) {
133
134
  return key % 2;
134
135
  }
135
136
 
137
+ usearch_key_t found_keys[10];
138
+ usearch_distance_t found_distances[10];
136
139
  usearch_filtered_search(
137
- index, &vector[0], usearch_scalar_f32_k, 10,
140
+ index, &query[0], usearch_scalar_f32_k, 10,
138
141
  &is_odd, NULL, // no state needed for this callback
139
142
  &found_keys[0], &found_distances[0], &error);
140
143
  ```
package/cpp/README.md CHANGED
@@ -71,8 +71,19 @@ Instead of spawning additional threads within USearch, we focus on the thread sa
71
71
 
72
72
  During initialization, we allocate enough temporary memory for all the cores on the machine.
73
73
  On the call, the user can supply the identifier of the current thread, making this library easy to integrate with OpenMP and similar tools.
74
+ Here is how parallel indexing may look like, when dealing with the low-level engine:
74
75
 
75
- Moreover, you can take advantage of one of the provided "executors" to parallelize the search:
76
+ ```cpp
77
+ std::size_t executor_threads = std::thread::hardware_concurrency() * 4;
78
+ executor_default_t executor(executor_threads);
79
+
80
+ index.reserve(index_limits_t {vectors.size(), executor.size()});
81
+ executor.fixed(vectors.size(), [&](std::size_t thread, std::size_t task) {
82
+ index.add(task, vectors[task + 3].data(), index_update_config_t { .thread = thread });
83
+ });
84
+ ```
85
+
86
+ Aside from the `executor_default_t`, you can take advantage of one of the provided "executors" to parallelize the search:
76
87
 
77
88
  - `executor_openmp_t`, that would use OpenMP under the hood.
78
89
  - `executor_stl_t`, that will spawn `std::thread` instances.
package/cpp/test.cpp CHANGED
@@ -114,8 +114,9 @@ void test_cosine(index_at& index, std::vector<std::vector<scalar_at>> const& vec
114
114
  expect(std::equal(vector_second, vector_second + dimensions, vec_recovered_from_load.data()));
115
115
  }
116
116
 
117
- // Try batch requests
118
- executor_default_t executor;
117
+ // Try batch requests, heavily obersubscribing the CPU cores
118
+ std::size_t executor_threads = std::thread::hardware_concurrency() * 4;
119
+ executor_default_t executor(executor_threads);
119
120
  index.reserve({vectors.size(), executor.size()});
120
121
  executor.fixed(vectors.size() - 3, [&](std::size_t thread, std::size_t task) {
121
122
  if constexpr (punned_ak) {
@@ -11,7 +11,7 @@
11
11
 
12
12
  #define USEARCH_VERSION_MAJOR 2
13
13
  #define USEARCH_VERSION_MINOR 11
14
- #define USEARCH_VERSION_PATCH 0
14
+ #define USEARCH_VERSION_PATCH 1
15
15
 
16
16
  // Inferring C++ version
17
17
  // https://stackoverflow.com/a/61552074
@@ -742,6 +742,11 @@ class index_dense_gt {
742
742
  unique_lock_t lock(slot_lookup_mutex_);
743
743
  slot_lookup_.reserve(limits.members);
744
744
  vectors_lookup_.resize(limits.members);
745
+
746
+ // During reserve, no insertions may be happening, so we can safely overwrite the whole collection.
747
+ std::unique_lock<std::mutex> available_threads_lock(available_threads_mutex_);
748
+ available_threads_.resize(limits.threads());
749
+ std::iota(available_threads_.begin(), available_threads_.end(), 0ul);
745
750
  }
746
751
  return typed_->reserve(limits);
747
752
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usearch",
3
- "version": "2.11.0",
3
+ "version": "2.11.1",
4
4
  "description": "Smaller & Faster Single-File Vector Search Engine from Unum",
5
5
  "author": "Ash Vardanian (https://ashvardanian.com/)",
6
6
  "license": "Apache 2.0",
Binary file
Binary file
Binary file
Binary file