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

Issue:
Feature: support for retrieving metadata from index file in C interface
### Describe what you are looking for

Hey, I found an unobvious behaviour with the Go code related to the index pointer and configuration. Here's what's going on:

1) In the Load function only the index pointer is set up, but the configuration is left with incorrect dimensions. https://github.com/unum-cloud/usearch/blob/e686a3dd66e2be5f7b376a56e779de90ce15c969/golang/lib.go#L341-L356

2) In the Get function, a vector is created with a length based on dimensions from config. https://github.com/unum-cloud/usearch/blob/e686a3dd66e2be5f7b376a56e779de90ce15c969/golang/lib.go#L289
    Same in the Search function https://github.com/unum-cloud/usearch/blob/e686a3dd66e2be5f7b376a56e779de90ce15c969/golang/lib.go#L306

3) The dimensions come from the config struct, but it looks like it might not be set up properly. Since the Load function doesn't set the dimensions, and user can forget to do that explicitly.

One solution might be to set dimensions in Load function. 

Or better to add support for metadata loading in C interface, as it is done for Python.

### Can you contribute to the implementation?

- [ ] I can contribute

### Is your feature request specific to a certain interface?

Other bindings

### Contact Details

_No response_

### 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:
Great catch! Should be an easy patch

Fix patch:
diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml
index e075831c..6873845d 100644
--- a/.github/workflows/prerelease.yml
+++ b/.github/workflows/prerelease.yml
@@ -20,7 +20,6 @@ jobs:
   test_ubuntu_gcc:
     name: Ubuntu (GCC 12)
     runs-on: ubuntu-22.04
-
     env:
       CC: gcc-12
       CXX: g++-12
@@ -109,7 +108,6 @@ jobs:
   test_ubuntu_clang:
     name: Ubuntu (Clang 16)
     runs-on: ubuntu-22.04
-
     env:
       CC: clang-16
       CXX: clang++-16
@@ -154,10 +152,11 @@ jobs:
         uses: actions/setup-node@v3
         with:
           node-version: 18
-      - name: Build JavaScript
-        run: npm install
-      - name: Test JavaScript
-        run: npm ci && npm test
+      - name: Build and test JavaScript
+        run: |
+          npm install
+          npm ci
+          npm test
 
         # C#
       - name: Setup .NET ${{ env.DOTNET_VERSION }}
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index ad7edf94..2452b822 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -130,6 +130,7 @@ jobs:
         uses: egor-tensin/setup-mingw@v2
         with:
           platform: ${{ matrix.arch }}
+          version: 12.2.0
 
       - name: Get version
         id: set_version
@@ -401,18 +402,34 @@ jobs:
     name: Publish JavaScript
     needs: versioning
     runs-on: ubuntu-22.04
+    env:
+      CC: gcc-12
+      CXX: g++-12
+
     steps:
       - uses: actions/checkout@v3
         with:
           ref: "main"
       - run: git submodule update --init --recursive
-      - uses: actions/setup-node@v3
+
+      - name: Update compilers
+        run: |
+          sudo apt update
+          sudo apt install -y cmake build-essential libjemalloc-dev libomp-dev gcc-12 g++-12
+
+      - name: Set up Node.js
+        uses: actions/setup-node@v3
         with:
           node-version: 18
-      - run: npm install
-      - run: npm ci
-      - run: npm test
-      - uses: JS-DevTools/npm-publish@v2
+
+      - name: Build and Test
+        run: |
+          npm install
+          npm ci
+          npm test
+
+      - name: Publish
+        uses: JS-DevTools/npm-publish@v2
         with:
           token: ${{ secrets.NPM_TOKEN }}
 
@@ -517,7 +534,7 @@ jobs:
           labels: ${{ steps.meta.outputs.labels }}
 
   build_csharp:
-    name: Build C#
+    name: Build C Dependencies for C#
     needs: versioning
     strategy:
       matrix:
@@ -532,31 +549,40 @@ jobs:
           ref: "main"
       - run: git submodule update --init --recursive
 
-      - name: Ubuntu/macOS build usearch shared obj
-        if: matrix.os == 'ubuntu-22.04' || matrix.os == 'macos-12'
-        run: ./build_lib.sh
-        working-directory: ${{ github.workspace }}/csharp/eng
-
-      - name: Windows MSVC build usearch dll
-        if: matrix.os == 'windows-2022'
+      - name: Build C library for Linux
+        if: matrix.os == 'ubuntu-22.04'
         run: |
-          call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
-          where cl
-          cl /EHsc /std:c++14 /O2 /LD /Fe:libusearch_c.dll lib.cpp /I. /I../include/ /I../fp16/include/
-        working-directory: ${{ github.workspace }}/c
-        shell: cmd
-
-      - name: Windows copy dll
+          sudo apt update
+          sudo apt install -y cmake build-essential libjemalloc-dev libomp-dev gcc-12 g++-12
+          cmake -B build_artifacts -DCMAKE_BUILD_TYPE=Release -DUSEARCH_BUILD_TEST_CPP=0 -DUSEARCH_BUILD_TEST_C=0 -DUSEARCH_BUILD_LIB_C=1 -DUSEARCH_USE_OPENMP=1 -DUSEARCH_USE_SIMSIMD=1 -DUSEARCH_USE_JEMALLOC=1
+          cmake --build build_artifacts --config Release
+          mkdir -p "${{ github.workspace }}/csharp/lib/runtimes/linux-x64/native"
+          cp "${{ github.workspace }}/build_artifacts/libusearch_c.so" "${{ github.workspace }}/csharp/lib/runtimes/linux-x64/native"
+
+      - name: Build C library for MacOS
+        if: matrix.os == 'macos-12'
+        run: |
+          brew update
+          brew install cmake
+          cmake -B build_artifacts -DCMAKE_BUILD_TYPE=Release -DUSEARCH_BUILD_TEST_CPP=0 -DUSEARCH_BUILD_TEST_C=0 -DUSEARCH_BUILD_LIB_C=1 -DUSEARCH_USE_OPENMP=0 -DUSEARCH_USE_SIMSIMD=1 -DUSEARCH_USE_JEMALLOC=0
+          cmake --build build_artifacts --config Release
+          mkdir -p "${{ github.workspace }}/csharp/lib/runtimes/osx-x64/native"
+          cp "${{ github.workspace }}/build_artifacts/libusearch_c.dylib" "${{ github.workspace }}/csharp/lib/runtimes/osx-x64/native"
+
+      - name: Build C library for Windows
         if: matrix.os == 'windows-2022'
         run: |
-          mkdir -p "${GITHUB_WORKSPACE}/csharp/lib/runtimes/win-x64/native"
-          cp "${GITHUB_WORKSPACE}/c/libusearch_c.dll" "${GITHUB_WORKSPACE}/csharp/lib/runtimes/win-x64/native"
+          choco install cmake
+          cmake -B build_artifacts -DCMAKE_BUILD_TYPE=Release -DUSEARCH_BUILD_TEST_CPP=0 -DUSEARCH_BUILD_TEST_C=0 -DUSEARCH_BUILD_LIB_C=1 -DUSEARCH_USE_OPENMP=0 -DUSEARCH_USE_SIMSIMD=0 -DUSEARCH_USE_JEMALLOC=0
+          cmake --build build_artifacts --config Release
+          mkdir -p "${{ github.workspace }}\csharp\lib\runtimes\win-x64\native"
+          cp "${{ github.workspace }}\build_artifacts\libusearch_c.dll" "${{ github.workspace }}\csharp\lib\runtimes\win-x64\native"
         shell: bash
 
       - name: Upload Artifacts
         uses: actions/upload-artifact@v3
         with:
-          name: usearch-libs
+          name: usearch-csharp-dependencies
           path: ${{ github.workspace }}/csharp/lib/**/*
 
   publish_csharp:
@@ -579,7 +605,7 @@ jobs:
       - name: Download usearch libs artifact
         uses: actions/download-artifact@v3
         with:
-          name: usearch-libs
+          name: usearch-csharp-dependencies
           path: ${{ env.USEARCH_LIBS }}
 
       - name: Setup .NET
diff --git a/c/lib.cpp b/c/lib.cpp
index b401d485..1ba12764 100644
--- a/c/lib.cpp
+++ b/c/lib.cpp
@@ -16,13 +16,13 @@ using labeling_result_t = typename index_dense_t::labeling_result_t;
 static_assert(std::is_same<usearch_key_t, index_dense_t::vector_key_t>::value, "Type mismatch between C and C++");
 static_assert(std::is_same<usearch_distance_t, index_dense_t::distance_t>::value, "Type mismatch between C and C++");
 
-// helper functions that are not part of the C ABI
-metric_kind_t to_native_metric(usearch_metric_kind_t kind) {
+metric_kind_t metric_kind_to_cpp(usearch_metric_kind_t kind) {
     switch (kind) {
     case usearch_metric_ip_k: return metric_kind_t::ip_k;
     case usearch_metric_l2sq_k: return metric_kind_t::l2sq_k;
     case usearch_metric_cos_k: return metric_kind_t::cos_k;
     case usearch_metric_haversine_k: return metric_kind_t::haversine_k;
+    case usearch_metric_divergence_k: return metric_kind_t::divergence_k;
     case usearch_metric_pearson_k: return metric_kind_t::pearson_k;
     case usearch_metric_jaccard_k: return metric_kind_t::jaccard_k;
     case usearch_metric_hamming_k: return metric_kind_t::hamming_k;
@@ -32,7 +32,22 @@ metric_kind_t to_native_metric(usearch_metric_kind_t kind) {
     }
 }
 
-scalar_kind_t to_native_scalar(usearch_scalar_kind_t kind) {
+usearch_metric_kind_t metric_kind_to_c(metric_kind_t kind) {
+    switch (kind) {
+    case metric_kind_t::ip_k: return usearch_metric_ip_k;
+    case metric_kind_t::l2sq_k: return usearch_metric_l2sq_k;
+    case metric_kind_t::cos_k: return usearch_metric_cos_k;
+    case metric_kind_t::haversine_k: return usearch_metric_haversine_k;
+    case metric_kind_t::divergence_k: return usearch_metric_divergence_k;
+    case metric_kind_t::pearson_k: return usearch_metric_pearson_k;
+    case metric_kind_t::jaccard_k: return usearch_metric_jaccard_k;
+    case metric_kind_t::hamming_k: return usearch_metric_hamming_k;
+    case metric_kind_t::tanimoto_k: return usearch_metric_tanimoto_k;
+    case metric_kind_t::sorensen_k: return usearch_metric_sorensen_k;
+    default: return usearch_metric_unknown_k;
+    }
+}
+scalar_kind_t scalar_kind_to_cpp(usearch_scalar_kind_t kind) {
     switch (kind) {
     case usearch_scalar_f32_k: return scalar_kind_t::f32_k;
     case usearch_scalar_f64_k: return scalar_kind_t::f64_k;
@@ -43,6 +58,17 @@ scalar_kind_t to_native_scalar(usearch_scalar_kind_t kind) {
     }
 }
 
+usearch_scalar_kind_t scalar_kind_to_c(scalar_kind_t kind) {
+    switch (kind) {
+    case scalar_kind_t::f32_k: return usearch_scalar_f32_k;
+    case scalar_kind_t::f64_k: return usearch_scalar_f64_k;
+    case scalar_kind_t::f16_k: return usearch_scalar_f16_k;
+    case scalar_kind_t::i8_k: return usearch_scalar_i8_k;
+    case scalar_kind_t::b1x8_k: return usearch_scalar_b1_k;
+    default: return usearch_scalar_unknown_k;
+    }
+}
+
 add_result_t add_(index_dense_t* index, usearch_key_t key, void const* vector, scalar_kind_t kind) {
     switch (kind) {
     case scalar_kind_t::f32_k: return index->add(key, (f32_t const*)vector);
@@ -84,8 +110,8 @@ USEARCH_EXPORT usearch_index_t usearch_init(usearch_init_options_t* options, use
 
     index_dense_config_t config(options->connectivity, options->expansion_add, options->expansion_search);
     config.multi = options->multi;
-    metric_kind_t metric_kind = to_native_metric(options->metric_kind);
-    scalar_kind_t scalar_kind = to_native_scalar(options->quantization);
+    metric_kind_t metric_kind = metric_kind_to_cpp(options->metric_kind);
+    scalar_kind_t scalar_kind = scalar_kind_to_cpp(options->quantization);
 
     metric_punned_t metric = //
         !options->metric ? metric_punned_t(options->dimensions, metric_kind, scalar_kind)
@@ -134,6 +160,24 @@ USEARCH_EXPORT void usearch_view(usearch_index_t index, char const* path, usearc
         *error = result.error.release();
 }
 
+USEARCH_EXPORT void usearch_metadata(char const* path, usearch_init_options_t* options, usearch_error_t* error) {
+
+    assert(path && options && error);
+    index_dense_metadata_result_t result = index_dense_metadata_from_path(path);
+    if (!result)
+        *error = result.error.release();
+
+    options->metric_kind = metric_kind_to_c(result.head.kind_metric);
+    options->quantization = scalar_kind_to_c(result.head.kind_scalar);
+    options->dimensions = result.head.dimensions;
+    options->multi = result.head.multi;
+
+    options->connectivity = 0;
+    options->expansion_add = 0;
+    options->expansion_search = 0;
+    options->metric = NULL;
+}
+
 USEARCH_EXPORT void usearch_save_buffer(usearch_index_t index, void* buffer, size_t length, usearch_error_t* error) {
 
     assert(index && buffer && length && error);
@@ -163,6 +207,26 @@ USEARCH_EXPORT void usearch_view_buffer(usearch_index_t index, void const* buffe
         *error = result.error.release();
 }
 
+USEARCH_EXPORT void usearch_metadata_buffer(void const* buffer, size_t length, usearch_init_options_t* options,
+                                            usearch_error_t* error) {
+
+    assert(buffer && length && options && error);
+    index_dense_metadata_result_t result =
+        index_dense_metadata_from_buffer(memory_mapped_file_t((byte_t*)(buffer), length));
+    if (!result)
+        *error = result.error.release();
+
+    options->metric_kind = metric_kind_to_c(result.head.kind_metric);
+    options->quantization = scalar_kind_to_c(result.head.kind_scalar);
+    options->dimensions = result.head.dimensions;
+    options->multi = result.head.multi;
+
+    options->connectivity = 0;
+    options->expansion_add = 0;
+    options->expansion_search = 0;
+    options->metric = NULL;
+}
+
 USEARCH_EXPORT size_t usearch_size(usearch_index_t index, usearch_error_t*) { //
     return reinterpret_cast<index_dense_t*>(index)->size();
 }
@@ -190,7 +254,7 @@ USEARCH_EXPORT void usearch_add(
     usearch_error_t* error) {
 
     assert(index && vector && error);
-    add_result_t result = add_(reinterpret_cast<index_dense_t*>(index), key, vector, to_native_scalar(kind));
+    add_result_t result = add_(reinterpret_cast<index_dense_t*>(index), key, vector, scalar_kind_to_cpp(kind));
     if (!result)
         *error = result.error.release();
 }
@@ -211,7 +275,7 @@ USEARCH_EXPORT size_t usearch_search(
 
     assert(index && vector && error);
     search_result_t result =
-        search_(reinterpret_cast<index_dense_t*>(index), vector, to_native_scalar(kind), results_limit);
+        search_(reinterpret_cast<index_dense_t*>(index), vector, scalar_kind_to_cpp(kind), results_limit);
     if (!result) {
         *error = result.error.release();
         return 0;
@@ -225,7 +289,7 @@ USEARCH_EXPORT size_t usearch_get(                          //
     void* vectors, usearch_scalar_kind_t kind, usearch_error_t*) {
 
     assert(index && vectors);
-    return get_(reinterpret_cast<index_dense_t*>(index), key, count, vectors, to_native_scalar(kind));
+    return get_(reinterpret_cast<index_dense_t*>(index), key, count, vectors, scalar_kind_to_cpp(kind));
 }
 
 USEARCH_EXPORT size_t usearch_remove(usearch_index_t index, usearch_key_t key, usearch_error_t* error) {
@@ -253,7 +317,7 @@ USEARCH_EXPORT usearch_distance_t usearch_distance(       //
     usearch_metric_kind_t metric_kind, usearch_error_t* error) {
 
     (void)error;
-    metric_punned_t metric(dimensions, to_native_metric(metric_kind), to_native_scalar(scalar_kind));
+    metric_punned_t metric(dimensions, metric_kind_to_cpp(metric_kind), scalar_kind_to_cpp(scalar_kind));
     return metric((byte_t const*)vector_first, (byte_t const*)vector_second);
 }
 
@@ -266,7 +330,7 @@ USEARCH_EXPORT void usearch_exact_search(                             //
     usearch_distance_t* distances, size_t distances_stride,           //
     usearch_error_t* error) {
 
-    metric_punned_t metric(dimensions, to_native_metric(metric_kind), to_native_scalar(scalar_kind));
+    metric_punned_t metric(dimensions, metric_kind_to_cpp(metric_kind), scalar_kind_to_cpp(scalar_kind));
     executor_default_t executor(threads);
     static exact_search_t search;
     exact_search_results_t result = search(                    //
diff --git a/c/usearch.h b/c/usearch.h
index 653a6bae..68bc2261 100644
--- a/c/usearch.h
+++ b/c/usearch.h
@@ -14,7 +14,8 @@ extern "C" {
 #endif
 
 #include <stdbool.h> // `bool`
-#include <stdint.h>  // `size_t`
+#include <stddef.h>  // `size_t`
+#include <stdint.h>  // `uint64_t`
 
 USEARCH_EXPORT typedef void* usearch_index_t;
 USEARCH_EXPORT typedef uint64_t usearch_key_t;
@@ -42,6 +43,7 @@ USEARCH_EXPORT typedef enum usearch_metric_kind_t {
     usearch_metric_ip_k,
     usearch_metric_l2sq_k,
     usearch_metric_haversine_k,
+    usearch_metric_divergence_k,
     usearch_metric_pearson_k,
     usearch_metric_jaccard_k,
     usearch_metric_hamming_k,
@@ -136,6 +138,15 @@ USEARCH_EXPORT void usearch_load(usearch_index_t, char const* path, usearch_erro
  */
 USEARCH_EXPORT void usearch_view(usearch_index_t, char const* path, usearch_error_t* error);
 
+/**
+ *  @brief Loads index metadata from a file.
+ *  @param[in] path The file path from where the index will be loaded.
+ *  @param[out] options Pointer to the `usearch_init_options_t` structure to be populated.
+ *  @param[out] error Pointer to a string where the error message will be stored, if an error occurs.
+ *  @return A handle to the initialized USearch index, or `NULL` on failure.
+ */
+USEARCH_EXPORT void usearch_metadata(char const* path, usearch_init_options_t* options, usearch_error_t* error);
+
 /**
  *  @brief Saves the index to an in-memory buffer.
  *  @param[in] buffer The in-memory continuous buffer where the index will be saved.
@@ -157,6 +168,16 @@ USEARCH_EXPORT void usearch_load_buffer(usearch_index_t, void const* buffer, siz
  */
 USEARCH_EXPORT void usearch_view_buffer(usearch_index_t, void const* buffer, size_t length, usearch_error_t* error);
 
+/**
+ *  @brief Loads index metadata from an in-memory buffer.
+ *  @param[in] path The file path from where the index will be loaded.
+ *  @param[out] options Pointer to the `usearch_init_options_t` structure to be populated.
+ *  @param[out] error Pointer to a string where the error message will be stored, if an error occurs.
+ *  @return A handle to the initialized USearch index, or `NULL` on failure.
+ */
+USEARCH_EXPORT void usearch_metadata_buffer(void const* buffer, size_t length, usearch_init_options_t* options,
+                                            usearch_error_t* error);
+
 USEARCH_EXPORT size_t usearch_size(usearch_index_t, usearch_error_t* error);
 USEARCH_EXPORT size_t usearch_capacity(usearch_index_t, usearch_error_t* error);
 USEARCH_EXPORT size_t usearch_dimensions(usearch_index_t, usearch_error_t* error);
diff --git a/cpp/README.md b/cpp/README.md
index 89c87150..9573f4f5 100644
--- a/cpp/README.md
+++ b/cpp/README.md
@@ -76,6 +76,7 @@ The following distances are pre-packaged:
 - `metric_sorensen_gt<scalar_t>` for "Dice-Sorensen" coefficient for bit-strings.
 - `metric_pearson_gt<scalar_t>` for "Pearson" correlation between probability distributions.
 - `metric_haversine_gt<scalar_t>` for "Haversine" or "Great Circle" distance between coordinates used in GIS applications.
+- `metric_divergence_gt<scalar_t>` for the "Jensen Shannon" similarity between probability distributions.
 
 ## Multi-Threading
 
diff --git a/cpp/bench.cpp b/cpp/bench.cpp
index 404c08d0..4b3efe60 100644
--- a/cpp/bench.cpp
+++ b/cpp/bench.cpp
@@ -447,6 +447,7 @@ struct args_t {
     bool metric_l2 = false;
     bool metric_cos = false;
     bool metric_haversine = false;
+    bool metric_divergence = false;
     bool metric_hamming = false;
     bool metric_tanimoto = false;
     bool metric_sorensen = false;
@@ -458,6 +459,8 @@ struct args_t {
             return metric_kind_t::cos_k;
         if (metric_haversine)
             return metric_kind_t::haversine_k;
+        if (metric_divergence)
+            return metric_kind_t::divergence_k;
         if (metric_hamming)
             return metric_kind_t::hamming_k;
         if (metric_tanimoto)
diff --git a/golang/lib.go b/golang/lib.go
index 2d86be87..7c5bb679 100644
--- a/golang/lib.go
+++ b/golang/lib.go
@@ -21,6 +21,7 @@ const (
 	Cosine
 	L2sq
 	Haversine
+	Divergence
 	Pearson
 	Hamming
 	Tanimoto
@@ -37,6 +38,8 @@ func (m Metric) String() string {
 		return "cos"
 	case Haversine:
 		return "haversine"
+	case Divergence:
+		return "divergence"
 	case Pearson:
 		return "pearson"
 	case Hamming:
diff --git a/include/usearch/index_plugins.hpp b/include/usearch/index_plugins.hpp
index 5f6ea7c7..f5a6640c 100644
--- a/include/usearch/index_plugins.hpp
+++ b/include/usearch/index_plugins.hpp
@@ -112,6 +112,7 @@ enum class metric_kind_t : std::uint8_t {
     // Custom:
     pearson_k = 'p',
     haversine_k = 'h',
+    divergence_k = 'd',
 
     // Sets:
     jaccard_k = 'j',
@@ -239,6 +240,7 @@ inline char const* metric_kind_name(metric_kind_t metric) noexcept {
     case metric_kind_t::l2sq_k: return "l2sq";
     case metric_kind_t::pearson_k: return "pearson";
     case metric_kind_t::haversine_k: return "haversine";
+    case metric_kind_t::divergence_k: return "divergence";
     case metric_kind_t::jaccard_k: return "jaccard";
     case metric_kind_t::hamming_k: return "hamming";
     case metric_kind_t::tanimoto_k: return "tanimoto";
@@ -275,6 +277,8 @@ inline expected_gt<metric_kind_t> metric_from_name(char const* name, std::size_t
         parsed.result = metric_kind_t::cos_k;
     } else if (str_equals(name, len, "haversine")) {
         parsed.result = metric_kind_t::haversine_k;
+    } else if (str_equals(name, len, "divergence")) {
+        parsed.result = metric_kind_t::divergence_k;
     } else if (str_equals(name, len, "pearson")) {
         parsed.result = metric_kind_t::pearson_k;
     } else if (str_equals(name, len, "hamming")) {
@@ -284,8 +288,8 @@ inline expected_gt<metric_kind_t> metric_from_name(char const* name, std::size_t
     } else if (str_equals(name, len, "sorensen")) {
         parsed.result = metric_kind_t::sorensen_k;
     } else
-        parsed.failed(
-            "Unknown distance, choose: l2sq, ip, cos, haversine, jaccard, pearson, hamming, tanimoto, sorensen");
+        parsed.failed("Unknown distance, choose: l2sq, ip, cos, haversine, divergence, jaccard, pearson, hamming, "
+                      "tanimoto, sorensen");
     return parsed;
 }
 
@@ -1180,6 +1184,32 @@ template <typename scalar_at = float, typename result_at = float> struct metric_
     }
 };
 
+/**
+ *  @brief  Measures Jensen-Shannon Divergence between two probability distributions.
+ */
+template <typename scalar_at = float, typename result_at = float> struct metric_divergence_gt {
+    using scalar_t = scalar_at;
+    using result_t = result_at;
+
+    inline result_t operator()(scalar_t const* p, scalar_t const* q, std::size_t dim) const noexcept {
+        result_t kld_pm{}, kld_qm{};
+        scalar_t epsilon = std::numeric_limits<scalar_t>::epsilon();
+#if USEARCH_USE_OPENMP
+#pragma omp simd reduction(+ : kld_pm, kld_qm)
+#elif defined(USEARCH_DEFINED_CLANG)
+#pragma clang loop vectorize(enable)
+#elif defined(USEARCH_DEFINED_GCC)
+#pragma GCC ivdep
+#endif
+        for (std::size_t i = 0; i != dim; ++i) {
+            result_t mi = result_t(p[i] + q[i]) / 2 + epsilon;
+            kld_pm += p[i] * std::log((p[i] + epsilon) / mi);
+            kld_qm += q[i] * std::log((q[i] + epsilon) / mi);
+        }
+        return (kld_pm + kld_qm) / 2;
+    }
+};
+
 struct cos_i8_t {
     using scalar_t = i8_t;
     using result_t = f32_t;
@@ -1452,6 +1482,17 @@ class metric_punned_t {
             }
             break;
         }
+        case metric_kind_t::divergence_k: {
+            switch (scalar_kind_) {
+            case scalar_kind_t::f16_k:
+                raw_ptr_ = (punned_ptr_t)&equidimensional_<metric_divergence_gt<f16_t, f32_t>>;
+                break;
+            case scalar_kind_t::f32_k: raw_ptr_ = (punned_ptr_t)&equidimensional_<metric_divergence_gt<f32_t>>; break;
+            case scalar_kind_t::f64_k: raw_ptr_ = (punned_ptr_t)&equidimensional_<metric_divergence_gt<f64_t>>; break;
+            default: raw_ptr_ = nullptr; break;
+            }
+            break;
+        }
         case metric_kind_t::jaccard_k: // Equivalent to Tanimoto
         case metric_kind_t::tanimoto_k:
             raw_ptr_ = (punned_ptr_t)&equidimensional_<metric_tanimoto_gt<b1x8_t>>,
diff --git a/java/cloud/unum/usearch/Index.java b/java/cloud/unum/usearch/Index.java
index 2da0fb61..85464297 100644
--- a/java/cloud/unum/usearch/Index.java
+++ b/java/cloud/unum/usearch/Index.java
@@ -1,83 +1,170 @@
 package cloud.unum.usearch;
 
 import java.io.File;
+import java.io.InputStream;
 import java.nio.file.Files;
 import java.nio.file.StandardCopyOption;
-import java.io.InputStream;
 
 /**
- * @brief Java bindings for Unum USearch.
- * 
- * @see Tutorials:
- *      https://nachtimwald.com/2017/06/06/wrapping-a-c-library-in-java/
- *      https://www3.ntu.edu.sg/home/ehchua/programming/java/javanativeinterface.html
+ * Java bindings for Unum USearch.
+ * <p>
+ * Provides interface to interact with USearch library and perform various operations related to indexing and searching.
+ *
+ * @see <a href="https://nachtimwald.com/2017/06/06/wrapping-a-c-library-in-java/">Wrapping a C library in Java</a>
+ * @see <a href="https://www3.ntu.edu.sg/home/ehchua/programming/java/javanativeinterface.html">Java Native Interface tutorial</a>
  */
 public class Index {
 
   private long c_ptr = 0;
 
-  public Index(String metric, String quantization, //
-      long dimensions, long capacity, long connectivity, //
-      long expansion_add, long expansion_search) {
-    c_ptr = c_create(
+  /**
+   * Creates a new instance of Index with specified parameters.
+   *
+   * @param metric          the metric type for distance calculation between vectors
+   * @param quantization    the scalar type for quantization of vector data
+   * @param dimensions      the number of dimensions in the vectors
+   * @param capacity        the initial capacity of the index
+   * @param connectivity    the connectivity parameter that limits connections-per-node in graph
+   * @param expansion_add   the expansion factor used for index construction when adding vectors
+   * @param expansion_search the expansion factor used for index construction during search operations
+   */
+  public Index(
+    String metric,
+    String quantization, //
+    long dimensions,
+    long capacity,
+    long connectivity, //
+    long expansion_add,
+    long expansion_search
+  ) {
+    c_ptr =
+      c_create(
         metric,
         quantization,
         dimensions,
         capacity,
         connectivity,
         expansion_add,
-        expansion_search);
+        expansion_search
+      );
   }
 
+  /**
+   * Retrieves the current size of the index.
+   *
+   * @return the number of vectors currently indexed.
+   */
   public long size() {
     return c_size(c_ptr);
   }
 
+  /**
+   * Retrieves the connectivity parameter of the index.
+   *
+   * @return the connectivity parameter that limits connections-per-node in graph.
+   */
   public long connectivity() {
     return c_connectivity(c_ptr);
   }
 
+  /**
+   * Retrieves the number of dimensions of the vectors in the index.
+   *
+   * @return the number of dimensions in the vectors.
+   */
   public long dimensions() {
     return c_dimensions(c_ptr);
   }
 
+  /**
+   * Retrieves the current capacity of the index.
+   *
+   * @return the total capacity including current size.
+   */
   public long capacity() {
     return c_capacity(c_ptr);
   }
 
+  /**
+   * Reserves memory for a specified number of incoming vectors.
+   *
+   * @param capacity the desired total capacity including current size.
+   */
   public void reserve(long capacity) {
     c_reserve(c_ptr, capacity);
   }
 
+  /**
+   * Adds a vector with a specified key to the index.
+   *
+   * @param key     the key associated with the vector
+   * @param vector  the vector data
+   */
   public void add(int key, float vector[]) {
     c_add(c_ptr, key, vector);
   }
 
+  /**
+   * Searches for closest vectors to the specified query vector.
+   *
+   * @param vector  the query vector data
+   * @param count   the number of nearest neighbors to search
+   * @return        an array of keys of the nearest neighbors
+   */
   public int[] search(float vector[], long count) {
     return c_search(c_ptr, vector, count);
   }
 
+  /**
+   * Saves the index to a file.
+   *
+   * @param path the file path where the index will be saved.
+   */
   public void save(String path) {
     c_save(c_ptr, path);
   }
 
+  /**
+   * Loads the index from a file.
+   *
+   * @param path the file path from where the index will be loaded.
+   */
   public void load(String path) {
     c_load(c_ptr, path);
   }
 
+  /**
+   * Creates a view of the index from a file without copying it into memory.
+   *
+   * @param path the file path from where the view will be created.
+   */
   public void view(String path) {
     c_view(c_ptr, path);
   }
 
+  /**
+   * Removes the vector associated with the given key from the index.
+   *
+   * @param key the key of the vector to be removed.
+   * @return {@code true} if the vector was successfully removed, {@code false} otherwise.
+   */
   public boolean remove(int key) {
     return c_remove(c_ptr, key);
   }
-  
+
+  /**
+   * Renames the vector to map to a different key.
+   *
+   * @param from the key of the vector to be renamed.
+   * @param to the new key for the vector.
+   * @return {@code true} if the vector was successfully renamed, {@code false} otherwise.
+   */
   public boolean rename(int from, int to) {
     return c_rename(c_ptr, from, to);
   }
 
   public static class Config {
+
     private String _metric = "ip";
     private String _quantization = "f32";
     private long _dimensions = 0;
@@ -86,18 +173,18 @@ public static class Config {
     private long _expansion_add = 0;
     private long _expansion_search = 0;
 
-    public Config() {
-    }
+    public Config() {}
 
     public Index build() {
       return new Index(
-          _metric,
-          _quantization,
-          _dimensions,
-          _capacity,
-          _connectivity,
-          _expansion_add,
-          _expansion_search);
+        _metric,
+        _quantization,
+        _dimensions,
+        _capacity,
+        _connectivity,
+        _expansion_add,
+        _expansion_search
+      );
     }
 
     public Config metric(String _metric) {
@@ -146,10 +233,15 @@ public static void main(String[] args) {
     System.out.println("Java tests passed!");
   }
 
-  private static native long c_create(//
-      String metric, String quantization, //
-      long dimensions, long capacity, long connectivity, //
-      long expansion_add, long expansion_search);
+  private static native long c_create( //
+    String metric,
+    String quantization, //
+    long dimensions,
+    long capacity,
+    long connectivity, //
+    long expansion_add,
+    long expansion_search
+  );
 
   private static native void c_destroy(long ptr);
 
@@ -176,4 +268,4 @@ private static native long c_create(//
   private static native boolean c_remove(long ptr, int key);
 
   private static native boolean c_rename(long ptr, int from, int to);
-}
\ No newline at end of file
+}
diff --git a/javascript/usearch.js b/javascript/usearch.js
index 066a2356..4f5191a2 100644
--- a/javascript/usearch.js
+++ b/javascript/usearch.js
@@ -11,6 +11,7 @@ const MetricKind = {
     IP: 'ip',
     L2sq: 'l2sq',
     Haversine: 'haversine',
+    Divergence: 'divergence',
     Pearson: 'pearson',
     Jaccard: 'jaccard',
     Hamming: 'hamming',
diff --git a/objc/USearchObjective.mm b/objc/USearchObjective.mm
index 4d01f72f..2ac024b4 100644
--- a/objc/USearchObjective.mm
+++ b/objc/USearchObjective.mm
@@ -33,6 +33,9 @@ metric_kind_t to_native_metric(USearchMetric m) {
         case USearchMetricHaversine:
             return metric_kind_t::haversine_k;
 
+        case USearchMetricDivergence:
+            return metric_kind_t::divergence_k;
+
         case USearchMetricJaccard:
             return metric_kind_t::jaccard_k;
 
diff --git a/objc/include/USearchObjective.h b/objc/include/USearchObjective.h
index 62b898c1..8cf33945 100644
--- a/objc/include/USearchObjective.h
+++ b/objc/include/USearchObjective.h
@@ -19,6 +19,7 @@ typedef NS_ENUM(NSUInteger, USearchMetric) {
     USearchMetricL2sq,
     USearchMetricPearson,
     USearchMetricHaversine,
+    USearchMetricDivergence,
     USearchMetricJaccard,
     USearchMetricHamming,
     USearchMetricTanimoto,
diff --git a/python/lib.cpp b/python/lib.cpp
index 67364604..ba2f887d 100644
--- a/python/lib.cpp
+++ b/python/lib.cpp
@@ -864,6 +864,7 @@ PYBIND11_MODULE(compiled, m) {
         .value("L2sq", metric_kind_t::l2sq_k)
 
         .value("Haversine", metric_kind_t::haversine_k)
+        .value("Divergence", metric_kind_t::divergence_k)
         .value("Pearson", metric_kind_t::pearson_k)
         .value("Jaccard", metric_kind_t::jaccard_k)
         .value("Hamming", metric_kind_t::hamming_k)
diff --git a/python/usearch/index.py b/python/usearch/index.py
index a3159bac..96df27c5 100644
--- a/python/usearch/index.py
+++ b/python/usearch/index.py
@@ -151,6 +151,7 @@ def _normalize_metric(metric) -> MetricKind:
             "l2sq": MetricKind.L2sq,
             "l2_sq": MetricKind.L2sq,
             "haversine": MetricKind.Haversine,
+            "divergence": MetricKind.Divergence,
             "pearson": MetricKind.Pearson,
             "hamming": MetricKind.Hamming,
             "tanimoto": MetricKind.Tanimoto,
diff --git a/rust/lib.cpp b/rust/lib.cpp
index a2004bb1..0dcb237f 100644
--- a/rust/lib.cpp
+++ b/rust/lib.cpp
@@ -107,6 +107,7 @@ metric_kind_t rust_to_cpp_metric(MetricKind value) {
     case MetricKind::Cos: return metric_kind_t::cos_k;
     case MetricKind::Pearson: return metric_kind_t::pearson_k;
     case MetricKind::Haversine: return metric_kind_t::haversine_k;
+    case MetricKind::Divergence: return metric_kind_t::divergence_k;
     case MetricKind::Hamming: return metric_kind_t::hamming_k;
     case MetricKind::Tanimoto: return metric_kind_t::tanimoto_k;
     case MetricKind::Sorensen: return metric_kind_t::sorensen_k;
diff --git a/simsimd b/simsimd
index 8f2c8881..cdf2c1fb 160000
--- a/simsimd
+++ b/simsimd
@@ -1,1 +1,1 @@
-Subproject commit 8f2c8881e440a55cfea246996984662623b4d5dd
+Subproject commit cdf2c1fb3c84f2a7bd71543eb13d6c0ccb46e9b2


Test patch: