Repository: unum-cloud/usearch (language: python)

Issue:
Bug: crash when hardware concurrency is exceeded
### Describe the bug

When attempting to add vectors to the index using a number of threads that execeeds the hardware concurrency as reported by std::thread::hardware_concurrency(), a crash happens. Stack trace from a test (with AddressSanitizer) is available at https://gist.githubusercontent.com/mbautin/80c0d87a0915e7da7b076a055a382b9e/raw

The root cause seems to be an underflow of the available_threads_ vector. It is initialized as follows:
```
        available_threads_.resize(std::thread::hardware_concurrency());
        std::iota(available_threads_.begin(), available_threads_.end(), 0ul);
```

The crash happens in this function:
```
    thread_lock_t thread_lock_(std::size_t thread_id) const {
        if (thread_id != any_thread())
            return {*this, thread_id, false};

        available_threads_mutex_.lock();
        thread_id = available_threads_.back();
        available_threads_.pop_back();
        available_threads_mutex_.unlock();
        return {*this, thread_id, true};
    }
```

It is invoked from the add_ function as follows:
```
    template <typename scalar_at>
    add_result_t add_(                             //
        vector_key_t key, scalar_at const* vector, //
        std::size_t thread, bool force_vector_copy, cast_t const& cast) {

        if (!multi() && contains(key))
            return add_result_t{}.failed("Duplicate keys not allowed in high-level wrappers");

        // Cast the vector, if needed for compatibility with `metric_`
        thread_lock_t lock = thread_lock_(thread);
```

The `add` function is invoked with `thread = any_thread()`.

I see that this whole mechanism is ultimately needed for providing a "context" object corresponding to the thread. The user could enumerate all the threads that will be performing any index insertion or search operations, and pass a 0-based index of that thread as the `thread` parameter. It could be possible to call the reserve() function to set the correct threads_add and threads_search, but it does not seem intuitive.

```
inline index_limits_t(std::size_t n, std::size_t t) noexcept : members(n), threads_add(t), threads_search(t) {}
```


### Steps to reproduce

Run a test that performs more simultaneous concurrent indexing operations than the hardware concurrency.
Example test: https://gist.githubusercontent.com/mbautin/6ef7cbbc18b818c1ab969be55a74e899/raw (implemented in YugabyteDB's test framework -- should be easy to extract as an independent test).


### Expected behavior

The system should not crash. It should allow any number of indexing threads to run.

### USearch version

2.10.5

### Operating System

AlmaLinux 8.8

### Hardware architecture

x86

### Which interface are you using?

C++ implementation

### Contact Details

mbautin@users.noreply.github.com

### Is there an existing issue for this?

- [X] I have searched the existing issues

### Code of Conduct

- [X] I agree to follow this project's Code of Conduct


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index e5f07a56..163ce14e 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -778,6 +778,10 @@ jobs:
           npm install -g jsdoc typedoc
       - name: Install USearch from PyPi
         run: pip install usearch
+      - name: Build CJS JS
+        run: |
+          npm ci --ignore-scripts
+          npm run build-js
       - name: Build documentation
         run: cd docs && doxygen conf.dox && make html
       - name: Copy assets
diff --git a/README.md b/README.md
index a02c362b..6ffa2bc7 100644
--- a/README.md
+++ b/README.md
@@ -94,9 +94,10 @@ USearch is compact and broadly compatible without sacrificing performance, prima
 | Supported metrics ²                          |         9 fixed metrics |               any metric |              extendible |
 | Supported languages ³                        |             C++, Python |             10 languages |                portable |
 | Supported ID types ⁴                         |          32-bit, 64-bit |   32-bit, 40-bit, 64-bit |               efficient |
-| Required dependencies ⁵                      |            BLAS, OpenMP |                        - |            light-weight |
-| Bindings ⁶                                   |                    SWIG |                   Native |             low-latency |
-| Python binding size ⁷                        | [~ 10 MB][faiss-weight] | [< 1 MB][usearch-weight] |              deployable |
+| Filtering ⁵                                  |               ban-lists |           any predicates |              composable |
+| Required dependencies ⁶                      |            BLAS, OpenMP |                        - |            light-weight |
+| Bindings ⁷                                   |                    SWIG |                   Native |             low-latency |
+| Python binding size ⁸                        | [~ 10 MB][faiss-weight] | [< 1 MB][usearch-weight] |              deployable |
 
 [sloc]: https://en.wikipedia.org/wiki/Source_lines_of_code
 [faiss-weight]: https://pypi.org/project/faiss-cpu/#files
@@ -107,9 +108,10 @@ USearch is compact and broadly compatible without sacrificing performance, prima
 > ² 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.
 > ³ With USearch, you can reuse the same preconstructed index in various programming languages.
 > ⁴ The 40-bit integer allows you to store 4B+ vectors without allocating 8 bytes for every neighbor reference in the proximity graph.
-> ⁵ Lack of obligatory dependencies makes USearch much more portable.
-> ⁶ Native bindings introduce lower call latencies than more straightforward approaches.
-> ⁷ Lighter bindings make downloads and deployments faster.
+> ⁵ 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.
+> ⁶ Lack of obligatory dependencies makes USearch much more portable.
+> ⁷ Native bindings introduce lower call latencies than more straightforward approaches.
+> ⁸ Lighter bindings make downloads and deployments faster.
 
 [intel-benchmarks]: https://www.unum.cloud/blog/2023-11-07-scaling-vector-search-with-intel
 
@@ -192,7 +194,7 @@ When compared to FAISS's `IndexFlatL2` in Google Colab, __[USearch may offer up
 - `faiss.IndexFlatL2`: __55.3 ms__.
 - `usearch.index.search`: __2.54 ms__.
 
-## User-Defined Functions
+## User-Defined Metrics
 
 While most vector search packages concentrate on just two metrics, "Inner Product distance" and "Euclidean distance", USearch allows arbitrary user-defined metrics.
 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
 [cppyy]: https://cppyy.readthedocs.io/en/latest/
 [peachpy]: https://github.com/Maratyszcza/PeachPy
 
+## Filtering and Predicate Functions
+
+Sometimes you may want to cross-reference search-results against some external database or filter them based on some criteria.
+In most engines, you'd have to manually perform paging requests, successively filtering the results.
+In USearch you can simply pass a predicate function to the search method, which will be applied directly during graph traversal.
+In Rust that would look like this:
+
+```rust
+let is_odd = |key: Key| key % 2 == 1;
+let query = vec![0.2, 0.1, 0.2, 0.1, 0.3];
+let results = index.filtered_search(&query, 10, is_odd).unwrap();
+assert!(
+    results.keys.iter().all(|&key| key % 2 == 1),
+    "All keys must be odd"
+);
+```
+
 ## Memory Efficiency, Downcasting, and Quantization
 
 Training a quantization model and dimension-reduction is a common approach to accelerate vector search.
diff --git a/c/README.md b/c/README.md
index 37711bc8..3180c386 100644
--- a/c/README.md
+++ b/c/README.md
@@ -42,7 +42,7 @@ int main() {
 
     // Search:
     usearch_key_t found_keys[10];
-    float found_distances[10];
+    usearch_distance_t found_distances[10];
     size_t found_count = usearch_search(
         index, &vector[0], usearch_scalar_f32_k, 10, 
         &found_keys[0], &found_distances[0], &error);
@@ -113,7 +113,8 @@ simsimd_distance_t callback(void const* a, void const* b, void* state) {
     // Your custom metric implementation here
 }
 
-usearch_change_metric(index, callback, NULL, usearch_metric_unknown_k, &error);
+void callback_state = NULL;
+usearch_change_metric(index, callback, callback_state, usearch_metric_unknown_k, &error);
 ```
 
 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) {
     return key % 2;
 }
 
+usearch_key_t found_keys[10];
+usearch_distance_t found_distances[10];
 usearch_filtered_search(
-    index, &vector[0], usearch_scalar_f32_k, 10, 
+    index, &query[0], usearch_scalar_f32_k, 10, 
     &is_odd, NULL, // no state needed for this callback
     &found_keys[0], &found_distances[0], &error);
 ```
diff --git a/cpp/README.md b/cpp/README.md
index ae0c78f6..df32e152 100644
--- a/cpp/README.md
+++ b/cpp/README.md
@@ -71,8 +71,19 @@ Instead of spawning additional threads within USearch, we focus on the thread sa
 
 During initialization, we allocate enough temporary memory for all the cores on the machine.
 On the call, the user can supply the identifier of the current thread, making this library easy to integrate with OpenMP and similar tools.
+Here is how parallel indexing may look like, when dealing with the low-level engine:
 
-Moreover, you can take advantage of one of the provided "executors" to parallelize the search:
+```cpp
+std::size_t executor_threads = std::thread::hardware_concurrency() * 4;
+executor_default_t executor(executor_threads);
+
+index.reserve(index_limits_t {vectors.size(), executor.size()});
+executor.fixed(vectors.size(), [&](std::size_t thread, std::size_t task) {
+    index.add(task, vectors[task + 3].data(), index_update_config_t { .thread = thread });
+});
+```
+
+Aside from the `executor_default_t`, you can take advantage of one of the provided "executors" to parallelize the search:
 
 - `executor_openmp_t`, that would use OpenMP under the hood.
 - `executor_stl_t`, that will spawn `std::thread` instances.
diff --git a/docs/conf.py b/docs/conf.py
index 25bba3c8..24417a54 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -52,6 +52,9 @@
 breathe_projects = {"USearch": "../build/xml"}
 breathe_default_project = "USearch"
 
-js_language = "typescript"
-js_source_path = "../javascript/usearch.ts"
-jsdoc_config_path = "../javascript/tsconfig-cjs.json"
+# To switch to TypeScript, uncomment the following lines:
+#
+#   js_language = "typescript"
+#   js_source_path = "../javascript/usearch.ts"
+#   jsdoc_config_path = "../javascript/tsconfig-cjs.json"
+js_source_path = "../javascript/dist/cjs/usearch.js"
diff --git a/include/usearch/index_dense.hpp b/include/usearch/index_dense.hpp
index 58851829..39ebcc09 100644
--- a/include/usearch/index_dense.hpp
+++ b/include/usearch/index_dense.hpp
@@ -742,6 +742,11 @@ class index_dense_gt {
             unique_lock_t lock(slot_lookup_mutex_);
             slot_lookup_.reserve(limits.members);
             vectors_lookup_.resize(limits.members);
+
+            // During reserve, no insertions may be happening, so we can safely overwrite the whole collection.
+            std::unique_lock<std::mutex> available_threads_lock(available_threads_mutex_);
+            available_threads_.resize(limits.threads());
+            std::iota(available_threads_.begin(), available_threads_.end(), 0ul);
         }
         return typed_->reserve(limits);
     }
diff --git a/rust/README.md b/rust/README.md
index 4b4d9f1a..3ae58294 100644
--- a/rust/README.md
+++ b/rust/README.md
@@ -124,8 +124,9 @@ let weighted_distance = Box::new(move |a: *const f32, b: *const f32| unsafe {
 
     let image_similarity = f32::cosine(a_slice[0..image_dimensions], b_slice[0..image_dimensions]);
     let text_similarity = f32::cosine(a_slice[image_dimensions..], b_slice[image_dimensions..]);
+    let similarity = image_weights * image_similarity + text_weights * text_similarity / (image_weights + text_weights);
     
-    (1.0 - image_similarity) * image_weights + (1.0 - text_similarity) * text_weights
+    1.0 - similarity
 });
 index.change_metric(weighted_distance);
 ```


Test patch:
diff --git a/cpp/test.cpp b/cpp/test.cpp
index 779cc77d..4226e67e 100644
--- a/cpp/test.cpp
+++ b/cpp/test.cpp
@@ -114,8 +114,9 @@ void test_cosine(index_at& index, std::vector<std::vector<scalar_at>> const& vec
         expect(std::equal(vector_second, vector_second + dimensions, vec_recovered_from_load.data()));
     }
 
-    // Try batch requests
-    executor_default_t executor;
+    // Try batch requests, heavily obersubscribing the CPU cores
+    std::size_t executor_threads = std::thread::hardware_concurrency() * 4;
+    executor_default_t executor(executor_threads);
     index.reserve({vectors.size(), executor.size()});
     executor.fixed(vectors.size() - 3, [&](std::size_t thread, std::size_t task) {
         if constexpr (punned_ak) {