usearch 2.4.1 → 2.5.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/.github/workflows/release.yml +4 -3
- package/CITATION.cff +1 -1
- package/Cargo.toml +1 -1
- package/README.md +1 -1
- package/conanfile.py +1 -1
- package/cpp/CMakeLists.txt +4 -0
- package/csharp/nuget/nuget-package.props +1 -1
- package/include/usearch/index.hpp +1 -1
- package/include/usearch/index_dense.hpp +12 -5
- package/include/usearch/index_plugins.hpp +2 -6
- package/package.json +1 -1
- package/python/lib.cpp +26 -23
- package/python/scripts/test_tooling.py +2 -2
- package/python/usearch/index.py +8 -3
- package/rust/lib.cpp +38 -31
- package/rust/lib.hpp +11 -7
- package/rust/lib.rs +303 -170
- package/setup.py +8 -2
- package/wasmer.toml +1 -1
|
@@ -591,8 +591,10 @@ jobs:
|
|
|
591
591
|
GITHUB_PACKAGES_SOURCE: https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json
|
|
592
592
|
steps:
|
|
593
593
|
- uses: actions/checkout@v3
|
|
594
|
+
with:
|
|
595
|
+
ref: 'main'
|
|
594
596
|
- run: git submodule update --init --recursive
|
|
595
|
-
|
|
597
|
+
|
|
596
598
|
- name: Download usearch libs artifact
|
|
597
599
|
uses: actions/download-artifact@v3
|
|
598
600
|
with:
|
|
@@ -606,8 +608,7 @@ jobs:
|
|
|
606
608
|
|
|
607
609
|
- name: Pack project
|
|
608
610
|
run: |
|
|
609
|
-
|
|
610
|
-
dotnet pack "${{ env.SOLUTION }}" -c Release -p:Version="$VERSION_CONTENT" --output ${{ env.NUGET_PACKAGES }}
|
|
611
|
+
dotnet pack "${{ env.SOLUTION }}" -c Release --output ${{ env.NUGET_PACKAGES }}
|
|
611
612
|
working-directory: ${{ github.workspace }}
|
|
612
613
|
|
|
613
614
|
- name: Publish to NuGet
|
package/CITATION.cff
CHANGED
package/Cargo.toml
CHANGED
package/README.md
CHANGED
package/conanfile.py
CHANGED
|
@@ -7,7 +7,7 @@ required_conan_version = ">=1.53.0"
|
|
|
7
7
|
class USearchConan(ConanFile):
|
|
8
8
|
|
|
9
9
|
name = "usearch"
|
|
10
|
-
version = '2.
|
|
10
|
+
version = '2.5.1'
|
|
11
11
|
license = "Apache-2.0"
|
|
12
12
|
description = "Smaller & Faster Single-File Vector Search Engine from Unum"
|
|
13
13
|
homepage = "https://github.com/unum-cloud/usearch"
|
package/cpp/CMakeLists.txt
CHANGED
|
@@ -20,6 +20,8 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
|
|
20
20
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native")
|
|
21
21
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fmax-errors=1")
|
|
22
22
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pedantic")
|
|
23
|
+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color=always")
|
|
24
|
+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ftime-report")
|
|
23
25
|
|
|
24
26
|
if(${USEARCH_USE_OPENMP})
|
|
25
27
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopenmp")
|
|
@@ -32,6 +34,8 @@ elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
|
|
32
34
|
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address -fsanitize=leak -fsanitize=alignment -fsanitize=undefined")
|
|
33
35
|
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wfatal-errors")
|
|
34
36
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pedantic")
|
|
37
|
+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcolor-diagnostics")
|
|
38
|
+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ftime-trace")
|
|
35
39
|
|
|
36
40
|
if(${USEARCH_USE_OPENMP})
|
|
37
41
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopenmp")
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
#pragma once
|
|
2
2
|
#include <stdlib.h> // `aligned_alloc`
|
|
3
3
|
|
|
4
|
-
#include <functional>
|
|
5
|
-
#include <numeric>
|
|
6
|
-
#include <
|
|
7
|
-
#include <
|
|
8
|
-
#include <vector> // `std::vector`
|
|
4
|
+
#include <functional> // `std::function`
|
|
5
|
+
#include <numeric> // `std::iota`
|
|
6
|
+
#include <thread> // `std::thread`
|
|
7
|
+
#include <vector> // `std::vector`
|
|
9
8
|
|
|
10
9
|
#include <usearch/index.hpp>
|
|
11
10
|
#include <usearch/index_plugins.hpp>
|
|
12
11
|
|
|
12
|
+
#if defined(USEARCH_DEFINED_CPP17)
|
|
13
|
+
#include <shared_mutex> // `std::shared_mutex`
|
|
14
|
+
#endif
|
|
15
|
+
|
|
13
16
|
namespace unum {
|
|
14
17
|
namespace usearch {
|
|
15
18
|
|
|
@@ -383,7 +386,11 @@ class index_dense_gt {
|
|
|
383
386
|
/// @brief Mutex, controlling concurrent access to `available_threads_`.
|
|
384
387
|
mutable std::mutex available_threads_mutex_;
|
|
385
388
|
|
|
389
|
+
#if defined(USEARCH_DEFINED_CPP17)
|
|
390
|
+
using shared_mutex_t = std::shared_mutex;
|
|
391
|
+
#else
|
|
386
392
|
using shared_mutex_t = unfair_shared_mutex_t;
|
|
393
|
+
#endif
|
|
387
394
|
using shared_lock_t = shared_lock_gt<shared_mutex_t>;
|
|
388
395
|
using unique_lock_t = std::unique_lock<shared_mutex_t>;
|
|
389
396
|
|
|
@@ -42,14 +42,10 @@
|
|
|
42
42
|
|
|
43
43
|
#if USEARCH_USE_SIMSIMD
|
|
44
44
|
#if defined(USEARCH_DEFINED_LINUX)
|
|
45
|
-
#define SIMSIMD_TARGET_X86_AVX2 1
|
|
46
|
-
#define SIMSIMD_TARGET_X86_AVX512 1
|
|
47
|
-
#define SIMSIMD_TARGET_ARM_NEON 1
|
|
48
|
-
#define SIMSIMD_TARGET_ARM_SVE 1
|
|
49
45
|
#include <simsimd/simsimd.h>
|
|
50
46
|
#elif defined(USEARCH_DEFINED_APPLE)
|
|
51
|
-
#define
|
|
52
|
-
#define
|
|
47
|
+
#define SIMSIMD_TARGET_X86_AVX512 0
|
|
48
|
+
#define SIMSIMD_TARGET_ARM_SVE 0
|
|
53
49
|
#include <simsimd/simsimd.h>
|
|
54
50
|
#endif
|
|
55
51
|
#endif
|
package/package.json
CHANGED
package/python/lib.cpp
CHANGED
|
@@ -526,22 +526,26 @@ static void search_typed_brute_force( //
|
|
|
526
526
|
byte_t const* dataset_data = reinterpret_cast<byte_t const*>(dataset_info.ptr);
|
|
527
527
|
byte_t const* queries_data = reinterpret_cast<byte_t const*>(queries_info.ptr);
|
|
528
528
|
for (std::size_t query_idx = 0; query_idx != queries_count; ++query_idx)
|
|
529
|
-
counts_py1d(query_idx) =
|
|
529
|
+
counts_py1d(query_idx) = wanted;
|
|
530
530
|
|
|
531
531
|
if (!threads)
|
|
532
532
|
threads = std::thread::hardware_concurrency();
|
|
533
533
|
|
|
534
534
|
std::size_t tasks_count = static_cast<std::size_t>(dataset_count * queries_count);
|
|
535
|
-
bitset_t query_mutexes(static_cast<std::size_t>(queries_count));
|
|
536
|
-
if (!query_mutexes)
|
|
537
|
-
throw std::bad_alloc();
|
|
538
535
|
|
|
539
536
|
// Progress status
|
|
540
537
|
progress_t progress_{progress};
|
|
541
538
|
std::atomic<std::size_t> processed{0};
|
|
542
539
|
|
|
540
|
+
// Allocate temporary memory to store the distance matrix
|
|
541
|
+
// Previous version didn't need temporary memory, but the performance was much lower
|
|
542
|
+
struct dense_key_and_distance_t {
|
|
543
|
+
u32_t offset;
|
|
544
|
+
f32_t distance;
|
|
545
|
+
};
|
|
546
|
+
std::vector<dense_key_and_distance_t> keys_and_distances(tasks_count);
|
|
547
|
+
|
|
543
548
|
executor_default_t{threads}.dynamic(tasks_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
544
|
-
//
|
|
545
549
|
std::size_t dataset_idx = task_idx / queries_count;
|
|
546
550
|
std::size_t query_idx = task_idx % queries_count;
|
|
547
551
|
|
|
@@ -549,24 +553,7 @@ static void search_typed_brute_force( //
|
|
|
549
553
|
byte_t const* query = queries_data + query_idx * queries_info.strides[0];
|
|
550
554
|
distance_t distance = metric(dataset, query);
|
|
551
555
|
|
|
552
|
-
{
|
|
553
|
-
auto lock = query_mutexes.lock(query_idx);
|
|
554
|
-
dense_key_t* keys = &keys_py2d(query_idx, 0);
|
|
555
|
-
distance_t* distances = &distances_py2d(query_idx, 0);
|
|
556
|
-
std::size_t& matches = reinterpret_cast<std::size_t&>(counts_py1d(query_idx));
|
|
557
|
-
if (matches == wanted)
|
|
558
|
-
if (distances[wanted - 1] <= distance)
|
|
559
|
-
return true;
|
|
560
|
-
|
|
561
|
-
std::size_t offset = std::lower_bound(distances, distances + matches, distance) - distances;
|
|
562
|
-
|
|
563
|
-
std::size_t count_worse = matches - offset - (wanted == matches);
|
|
564
|
-
std::memmove(keys + offset + 1, keys + offset, count_worse * sizeof(dense_key_t));
|
|
565
|
-
std::memmove(distances + offset + 1, distances + offset, count_worse * sizeof(distance_t));
|
|
566
|
-
keys[offset] = static_cast<dense_key_t>(dataset_idx);
|
|
567
|
-
distances[offset] = distance;
|
|
568
|
-
matches += matches != wanted;
|
|
569
|
-
}
|
|
556
|
+
keys_and_distances[task_idx] = {static_cast<u32_t>(query_idx), static_cast<f32_t>(distance)};
|
|
570
557
|
|
|
571
558
|
// We don't want to check for signals from multiple threads
|
|
572
559
|
++processed;
|
|
@@ -576,6 +563,20 @@ static void search_typed_brute_force( //
|
|
|
576
563
|
return true;
|
|
577
564
|
});
|
|
578
565
|
|
|
566
|
+
// Partial-sort every query result
|
|
567
|
+
executor_default_t{threads}.fixed(queries_count, [&](std::size_t, std::size_t query_idx) {
|
|
568
|
+
auto start = keys_and_distances.data() + query_idx * dataset_count;
|
|
569
|
+
std::partial_sort(start, start + wanted, start + dataset_count,
|
|
570
|
+
[](dense_key_and_distance_t const& a, dense_key_and_distance_t const& b) {
|
|
571
|
+
return a.distance < b.distance;
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
dense_key_t* keys = &keys_py2d(query_idx, 0);
|
|
575
|
+
distance_t* distances = &distances_py2d(query_idx, 0);
|
|
576
|
+
for (std::size_t i = 0; i != wanted; ++i)
|
|
577
|
+
keys[i] = static_cast<dense_key_t>(start[i].offset), distances[i] = start[i].distance;
|
|
578
|
+
});
|
|
579
|
+
|
|
579
580
|
// At the end report the latest numbers, because the reporter thread may be finished earlier
|
|
580
581
|
progress_(processed.load(), tasks_count);
|
|
581
582
|
}
|
|
@@ -602,6 +603,8 @@ static py::tuple search_many_brute_force( //
|
|
|
602
603
|
Py_ssize_t queries_dimensions = queries_info.shape[1];
|
|
603
604
|
if (dataset_dimensions != queries_dimensions)
|
|
604
605
|
throw std::invalid_argument("The number of vector dimensions doesn't match!");
|
|
606
|
+
if (wanted > dataset_count)
|
|
607
|
+
throw std::invalid_argument("You can't request more matches than in the dataset!");
|
|
605
608
|
|
|
606
609
|
scalar_kind_t dataset_kind = numpy_string_to_kind(dataset_info.format);
|
|
607
610
|
scalar_kind_t queries_kind = numpy_string_to_kind(queries_info.format);
|
|
@@ -56,13 +56,13 @@ def test_exact_search(rows: int, cols: int):
|
|
|
56
56
|
:param int cols: The number of columns in the matrix.
|
|
57
57
|
"""
|
|
58
58
|
original = np.random.rand(rows, cols)
|
|
59
|
-
matches: BatchMatches = search(original, original, 10, exact=True)
|
|
59
|
+
matches: BatchMatches = search(original, original, min(10, rows), exact=True)
|
|
60
60
|
top_matches = (
|
|
61
61
|
[int(m.keys[0]) for m in matches] if rows > 1 else int(matches.keys[0])
|
|
62
62
|
)
|
|
63
63
|
assert np.all(top_matches == np.arange(rows))
|
|
64
64
|
|
|
65
|
-
matches: Matches = search(original, original[0], 10, exact=True)
|
|
65
|
+
matches: Matches = search(original, original[0], min(10, rows), exact=True)
|
|
66
66
|
top_match = int(matches.keys[0])
|
|
67
67
|
assert top_match == 0
|
|
68
68
|
|
package/python/usearch/index.py
CHANGED
|
@@ -871,6 +871,10 @@ class Index:
|
|
|
871
871
|
def serialized_length(self) -> int:
|
|
872
872
|
return self._compiled.serialized_length
|
|
873
873
|
|
|
874
|
+
@property
|
|
875
|
+
def metric_kind(self) -> Union[MetricKind, CompiledMetric]:
|
|
876
|
+
return self._metric_jit if self._metric_jit.kind else self._metric_kind
|
|
877
|
+
|
|
874
878
|
@property
|
|
875
879
|
def metric(self) -> Union[MetricKind, CompiledMetric]:
|
|
876
880
|
return self._metric_jit if self._metric_jit else self._metric_kind
|
|
@@ -1190,6 +1194,7 @@ class Index:
|
|
|
1190
1194
|
"size": self.size,
|
|
1191
1195
|
"jit": self.jit,
|
|
1192
1196
|
"hardware_acceleration": self.hardware_acceleration,
|
|
1197
|
+
"metric_kind": self.metric_kind,
|
|
1193
1198
|
"dtype": self.dtype,
|
|
1194
1199
|
"path": self.path,
|
|
1195
1200
|
"compiled_with_openmp": USES_OPENMP,
|
|
@@ -1204,7 +1209,7 @@ class Index:
|
|
|
1204
1209
|
return f.format(
|
|
1205
1210
|
self.dtype,
|
|
1206
1211
|
self.ndim,
|
|
1207
|
-
self.
|
|
1212
|
+
self.metric_kind,
|
|
1208
1213
|
self.connectivity,
|
|
1209
1214
|
self.expansion_add,
|
|
1210
1215
|
self.expansion_search,
|
|
@@ -1225,7 +1230,7 @@ class Index:
|
|
|
1225
1230
|
"- config",
|
|
1226
1231
|
f"-- data type: {self.dtype}",
|
|
1227
1232
|
f"-- dimensions: {self.ndim}",
|
|
1228
|
-
f"-- metric: {self.
|
|
1233
|
+
f"-- metric: {self.metric_kind}",
|
|
1229
1234
|
f"-- connectivity: {self.connectivity}",
|
|
1230
1235
|
f"-- expansion on addition:{self.expansion_add} candidates",
|
|
1231
1236
|
f"-- expansion on search: {self.expansion_search} candidates",
|
|
@@ -1233,7 +1238,7 @@ class Index:
|
|
|
1233
1238
|
f"-- uses OpenMP: {USES_OPENMP}",
|
|
1234
1239
|
f"-- uses SimSIMD: {USES_SIMSIMD}",
|
|
1235
1240
|
f"-- supports half-precision: {USES_NATIVE_F16}",
|
|
1236
|
-
f"-- uses hardware
|
|
1241
|
+
f"-- uses hardware acceleration: {self.hardware_acceleration}",
|
|
1237
1242
|
"- state",
|
|
1238
1243
|
f"-- size: {self.size:,} vectors",
|
|
1239
1244
|
f"-- memory usage: {self.memory_usage:,} bytes",
|
package/rust/lib.cpp
CHANGED
|
@@ -23,65 +23,71 @@ template <typename scalar_at> Matches search_(index_dense_t& index, scalar_at co
|
|
|
23
23
|
return matches;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
NativeIndex::NativeIndex(std::unique_ptr<index_t> index) : index_(std::move(index)) {}
|
|
27
27
|
|
|
28
28
|
// clang-format off
|
|
29
|
-
void
|
|
30
|
-
void
|
|
31
|
-
void
|
|
32
|
-
void
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
Matches
|
|
36
|
-
Matches
|
|
37
|
-
Matches
|
|
38
|
-
|
|
39
|
-
|
|
29
|
+
void NativeIndex::add_i8(key_t key, rust::Slice<int8_t const> vec) const { index_->add(key, vec.data()).error.raise(); }
|
|
30
|
+
void NativeIndex::add_f16(key_t key, rust::Slice<uint16_t const> vec) const { index_->add(key, (f16_t const*)vec.data()).error.raise(); }
|
|
31
|
+
void NativeIndex::add_f32(key_t key, rust::Slice<float const> vec) const { index_->add(key, vec.data()).error.raise(); }
|
|
32
|
+
void NativeIndex::add_f64(key_t key, rust::Slice<double const> vec) const { index_->add(key, vec.data()).error.raise(); }
|
|
33
|
+
|
|
34
|
+
Matches NativeIndex::search_i8(rust::Slice<int8_t const> vec, size_t count) const { return search_(*index_, vec.data(), count); }
|
|
35
|
+
Matches NativeIndex::search_f16(rust::Slice<uint16_t const> vec, size_t count) const { return search_(*index_, (f16_t const*)vec.data(), count); }
|
|
36
|
+
Matches NativeIndex::search_f32(rust::Slice<float const> vec, size_t count) const { return search_(*index_, vec.data(), count); }
|
|
37
|
+
Matches NativeIndex::search_f64(rust::Slice<double const> vec, size_t count) const { return search_(*index_, vec.data(), count); }
|
|
38
|
+
|
|
39
|
+
size_t NativeIndex::get_i8(key_t key, rust::Slice<int8_t> vec) const { return index_->get(key, vec.data(), vec.size() / dimensions()); }
|
|
40
|
+
size_t NativeIndex::get_f16(key_t key, rust::Slice<uint16_t> vec) const { return index_->get(key, (f16_t*)vec.data(), vec.size() / dimensions()); }
|
|
41
|
+
size_t NativeIndex::get_f32(key_t key, rust::Slice<float> vec) const { return index_->get(key, vec.data(), vec.size() / dimensions()); }
|
|
42
|
+
size_t NativeIndex::get_f64(key_t key, rust::Slice<double> vec) const { return index_->get(key, vec.data(), vec.size() / dimensions()); }
|
|
40
43
|
// clang-format on
|
|
41
44
|
|
|
42
|
-
|
|
45
|
+
size_t NativeIndex::remove(key_t key) const {
|
|
43
46
|
labeling_result_t result = index_->remove(key);
|
|
44
47
|
result.error.raise();
|
|
45
48
|
return result.completed;
|
|
46
49
|
}
|
|
47
50
|
|
|
48
|
-
|
|
51
|
+
size_t NativeIndex::rename(key_t from, key_t to) const {
|
|
49
52
|
labeling_result_t result = index_->rename(from, to);
|
|
50
53
|
result.error.raise();
|
|
51
54
|
return result.completed;
|
|
52
55
|
}
|
|
53
56
|
|
|
54
|
-
|
|
57
|
+
size_t NativeIndex::count(key_t key) const { return index_->count(key); }
|
|
58
|
+
bool NativeIndex::contains(key_t key) const { return index_->contains(key); }
|
|
55
59
|
|
|
56
|
-
void
|
|
60
|
+
void NativeIndex::reserve(size_t capacity) const { index_->reserve(capacity); }
|
|
57
61
|
|
|
58
|
-
size_t
|
|
59
|
-
size_t
|
|
60
|
-
size_t
|
|
61
|
-
size_t
|
|
62
|
-
size_t
|
|
62
|
+
size_t NativeIndex::dimensions() const { return index_->dimensions(); }
|
|
63
|
+
size_t NativeIndex::connectivity() const { return index_->connectivity(); }
|
|
64
|
+
size_t NativeIndex::size() const { return index_->size(); }
|
|
65
|
+
size_t NativeIndex::capacity() const { return index_->capacity(); }
|
|
66
|
+
size_t NativeIndex::serialized_length() const { return index_->serialized_length(); }
|
|
63
67
|
|
|
64
|
-
void
|
|
65
|
-
void
|
|
66
|
-
void
|
|
68
|
+
void NativeIndex::save(rust::Str path) const { index_->save(output_file_t(std::string(path).c_str())).error.raise(); }
|
|
69
|
+
void NativeIndex::load(rust::Str path) const { index_->load(input_file_t(std::string(path).c_str())).error.raise(); }
|
|
70
|
+
void NativeIndex::view(rust::Str path) const {
|
|
71
|
+
index_->view(memory_mapped_file_t(std::string(path).c_str())).error.raise();
|
|
72
|
+
}
|
|
67
73
|
|
|
68
|
-
void
|
|
74
|
+
void NativeIndex::save_to_buffer(rust::Slice<uint8_t> buffer) const {
|
|
69
75
|
index_->save(memory_mapped_file_t((byte_t*)buffer.data(), buffer.size())).error.raise();
|
|
70
76
|
}
|
|
71
77
|
|
|
72
|
-
void
|
|
78
|
+
void NativeIndex::load_from_buffer(rust::Slice<uint8_t const> buffer) const {
|
|
73
79
|
index_->load(memory_mapped_file_t((byte_t*)buffer.data(), buffer.size())).error.raise();
|
|
74
80
|
}
|
|
75
81
|
|
|
76
|
-
void
|
|
82
|
+
void NativeIndex::view_from_buffer(rust::Slice<uint8_t const> buffer) const {
|
|
77
83
|
index_->view(memory_mapped_file_t((byte_t*)buffer.data(), buffer.size())).error.raise();
|
|
78
84
|
}
|
|
79
85
|
|
|
80
|
-
std::unique_ptr<
|
|
86
|
+
std::unique_ptr<NativeIndex> wrap(index_t&& index) {
|
|
81
87
|
std::unique_ptr<index_t> punned_ptr;
|
|
82
88
|
punned_ptr.reset(new index_t(std::move(index)));
|
|
83
|
-
std::unique_ptr<
|
|
84
|
-
result.reset(new
|
|
89
|
+
std::unique_ptr<NativeIndex> result;
|
|
90
|
+
result.reset(new NativeIndex(std::move(punned_ptr)));
|
|
85
91
|
return result;
|
|
86
92
|
}
|
|
87
93
|
|
|
@@ -110,10 +116,11 @@ scalar_kind_t rust_to_cpp_scalar(ScalarKind value) {
|
|
|
110
116
|
}
|
|
111
117
|
}
|
|
112
118
|
|
|
113
|
-
std::unique_ptr<
|
|
119
|
+
std::unique_ptr<NativeIndex> new_native_index(IndexOptions const& options) {
|
|
114
120
|
metric_kind_t metric_kind = rust_to_cpp_metric(options.metric);
|
|
115
121
|
scalar_kind_t scalar_kind = rust_to_cpp_scalar(options.quantization);
|
|
116
122
|
metric_punned_t metric(options.dimensions, metric_kind, scalar_kind);
|
|
117
123
|
index_dense_config_t config(options.connectivity, options.expansion_add, options.expansion_search);
|
|
124
|
+
config.multi = options.multi;
|
|
118
125
|
return wrap(index_t::make(metric, config));
|
|
119
126
|
}
|
package/rust/lib.hpp
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
struct Matches;
|
|
9
9
|
struct IndexOptions;
|
|
10
10
|
|
|
11
|
-
class
|
|
11
|
+
class NativeIndex {
|
|
12
12
|
public:
|
|
13
13
|
using metric_t = unum::usearch::metric_punned_t;
|
|
14
14
|
using distance_t = unum::usearch::distance_punned_t;
|
|
@@ -17,25 +17,29 @@ class Index {
|
|
|
17
17
|
using search_result_t = typename index_dense_t::search_result_t;
|
|
18
18
|
using key_t = typename index_dense_t::key_t;
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
NativeIndex(std::unique_ptr<index_dense_t> index);
|
|
21
21
|
|
|
22
22
|
void reserve(size_t) const;
|
|
23
23
|
|
|
24
|
-
void add(key_t key, rust::Slice<float const> vector) const;
|
|
25
24
|
void add_i8(key_t key, rust::Slice<int8_t const> vector) const;
|
|
26
25
|
void add_f16(key_t key, rust::Slice<uint16_t const> vector) const;
|
|
27
26
|
void add_f32(key_t key, rust::Slice<float const> vector) const;
|
|
28
27
|
void add_f64(key_t key, rust::Slice<double const> vector) const;
|
|
29
28
|
|
|
30
|
-
Matches search(rust::Slice<float const> vector, size_t count) const;
|
|
31
29
|
Matches search_i8(rust::Slice<int8_t const> vector, size_t count) const;
|
|
32
30
|
Matches search_f16(rust::Slice<uint16_t const> vector, size_t count) const;
|
|
33
31
|
Matches search_f32(rust::Slice<float const> vector, size_t count) const;
|
|
34
32
|
Matches search_f64(rust::Slice<double const> vector, size_t count) const;
|
|
35
33
|
|
|
36
|
-
|
|
34
|
+
size_t get_i8(key_t key, rust::Slice<int8_t> vector) const;
|
|
35
|
+
size_t get_f16(key_t key, rust::Slice<uint16_t> vector) const;
|
|
36
|
+
size_t get_f32(key_t key, rust::Slice<float> vector) const;
|
|
37
|
+
size_t get_f64(key_t key, rust::Slice<double> vector) const;
|
|
38
|
+
|
|
39
|
+
size_t count(key_t key) const;
|
|
40
|
+
size_t remove(key_t key) const;
|
|
41
|
+
size_t rename(key_t from, key_t to) const;
|
|
37
42
|
bool contains(key_t key) const;
|
|
38
|
-
bool rename(key_t from, key_t to) const;
|
|
39
43
|
|
|
40
44
|
size_t dimensions() const;
|
|
41
45
|
size_t connectivity() const;
|
|
@@ -55,4 +59,4 @@ class Index {
|
|
|
55
59
|
std::unique_ptr<index_dense_t> index_;
|
|
56
60
|
};
|
|
57
61
|
|
|
58
|
-
std::unique_ptr<
|
|
62
|
+
std::unique_ptr<NativeIndex> new_native_index(IndexOptions const& options);
|
package/rust/lib.rs
CHANGED
|
@@ -33,198 +33,331 @@ pub mod ffi {
|
|
|
33
33
|
connectivity: usize,
|
|
34
34
|
expansion_add: usize,
|
|
35
35
|
expansion_search: usize,
|
|
36
|
+
multi: bool,
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
// C++ types and signatures exposed to Rust.
|
|
39
40
|
unsafe extern "C++" {
|
|
40
41
|
include!("lib.hpp");
|
|
41
42
|
|
|
42
|
-
///
|
|
43
|
-
type
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
pub fn
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
pub fn
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
pub fn
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
pub fn
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
pub fn
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
pub fn
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
43
|
+
/// Low-level C++ interface, that is further wrapped into the high-level `Index`
|
|
44
|
+
type NativeIndex;
|
|
45
|
+
|
|
46
|
+
pub fn new_native_index(options: &IndexOptions) -> Result<UniquePtr<NativeIndex>>;
|
|
47
|
+
pub fn reserve(self: &NativeIndex, capacity: usize) -> Result<()>;
|
|
48
|
+
pub fn dimensions(self: &NativeIndex) -> usize;
|
|
49
|
+
pub fn connectivity(self: &NativeIndex) -> usize;
|
|
50
|
+
pub fn size(self: &NativeIndex) -> usize;
|
|
51
|
+
pub fn capacity(self: &NativeIndex) -> usize;
|
|
52
|
+
pub fn serialized_length(self: &NativeIndex) -> usize;
|
|
53
|
+
|
|
54
|
+
pub fn add_i8(self: &NativeIndex, key: u64, vector: &[i8]) -> Result<()>;
|
|
55
|
+
pub fn add_f16(self: &NativeIndex, key: u64, vector: &[u16]) -> Result<()>;
|
|
56
|
+
pub fn add_f32(self: &NativeIndex, key: u64, vector: &[f32]) -> Result<()>;
|
|
57
|
+
pub fn add_f64(self: &NativeIndex, key: u64, vector: &[f64]) -> Result<()>;
|
|
58
|
+
|
|
59
|
+
pub fn search_i8(self: &NativeIndex, query: &[i8], count: usize) -> Result<Matches>;
|
|
60
|
+
pub fn search_f16(self: &NativeIndex, query: &[u16], count: usize) -> Result<Matches>;
|
|
61
|
+
pub fn search_f32(self: &NativeIndex, query: &[f32], count: usize) -> Result<Matches>;
|
|
62
|
+
pub fn search_f64(self: &NativeIndex, query: &[f64], count: usize) -> Result<Matches>;
|
|
63
|
+
|
|
64
|
+
pub fn get_i8(self: &NativeIndex, key: u64, buffer: &mut [i8]) -> Result<usize>;
|
|
65
|
+
pub fn get_f16(self: &NativeIndex, key: u64, buffer: &mut [u16]) -> Result<usize>;
|
|
66
|
+
pub fn get_f32(self: &NativeIndex, key: u64, buffer: &mut [f32]) -> Result<usize>;
|
|
67
|
+
pub fn get_f64(self: &NativeIndex, key: u64, buffer: &mut [f64]) -> Result<usize>;
|
|
68
|
+
|
|
69
|
+
pub fn remove(self: &NativeIndex, key: u64) -> Result<usize>;
|
|
70
|
+
pub fn rename(self: &NativeIndex, from: u64, to: u64) -> Result<usize>;
|
|
71
|
+
pub fn contains(self: &NativeIndex, key: u64) -> bool;
|
|
72
|
+
pub fn count(self: &NativeIndex, key: u64) -> usize;
|
|
73
|
+
|
|
74
|
+
pub fn save(self: &NativeIndex, path: &str) -> Result<()>;
|
|
75
|
+
pub fn load(self: &NativeIndex, path: &str) -> Result<()>;
|
|
76
|
+
pub fn view(self: &NativeIndex, path: &str) -> Result<()>;
|
|
77
|
+
pub fn save_to_buffer(self: &NativeIndex, buffer: &mut [u8]) -> Result<()>;
|
|
78
|
+
pub fn load_from_buffer(self: &NativeIndex, buffer: &[u8]) -> Result<()>;
|
|
79
|
+
pub fn view_from_buffer(self: &NativeIndex, buffer: &[u8]) -> Result<()>;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
pub struct Index {
|
|
84
|
+
inner: cxx::UniquePtr<ffi::NativeIndex>,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
impl Default for ffi::IndexOptions {
|
|
88
|
+
fn default() -> Self {
|
|
89
|
+
use crate::ffi::MetricKind;
|
|
90
|
+
use crate::ffi::ScalarKind;
|
|
91
|
+
Self {
|
|
92
|
+
dimensions: 128,
|
|
93
|
+
metric: MetricKind::IP,
|
|
94
|
+
quantization: ScalarKind::F32,
|
|
95
|
+
connectivity: 32,
|
|
96
|
+
expansion_add: 2,
|
|
97
|
+
expansion_search: 3,
|
|
98
|
+
multi: false,
|
|
95
99
|
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
96
102
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
103
|
+
pub trait VectorType {
|
|
104
|
+
fn add(index: &Index, key: u64, vector: &[Self]) -> Result<(), cxx::Exception>
|
|
105
|
+
where
|
|
106
|
+
Self: Sized;
|
|
107
|
+
fn get(index: &Index, key: u64, buffer: &mut [Self]) -> Result<usize, cxx::Exception>
|
|
108
|
+
where
|
|
109
|
+
Self: Sized;
|
|
110
|
+
fn search(index: &Index, query: &[Self], count: usize) -> Result<ffi::Matches, cxx::Exception>
|
|
111
|
+
where
|
|
112
|
+
Self: Sized;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
impl VectorType for i8 {
|
|
116
|
+
fn search(index: &Index, query: &[Self], count: usize) -> Result<ffi::Matches, cxx::Exception> {
|
|
117
|
+
index.inner.search_i8(query, count)
|
|
118
|
+
}
|
|
119
|
+
fn get(index: &Index, key: u64, vector: &mut [Self]) -> Result<usize, cxx::Exception> {
|
|
120
|
+
index.inner.get_i8(key, vector)
|
|
121
|
+
}
|
|
122
|
+
fn add(index: &Index, key: u64, vector: &[Self]) -> Result<(), cxx::Exception> {
|
|
123
|
+
index.inner.add_i8(key, vector)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
impl VectorType for f32 {
|
|
128
|
+
fn search(index: &Index, query: &[Self], count: usize) -> Result<ffi::Matches, cxx::Exception> {
|
|
129
|
+
index.inner.search_f32(query, count)
|
|
130
|
+
}
|
|
131
|
+
fn get(index: &Index, key: u64, vector: &mut [Self]) -> Result<usize, cxx::Exception> {
|
|
132
|
+
index.inner.get_f32(key, vector)
|
|
133
|
+
}
|
|
134
|
+
fn add(index: &Index, key: u64, vector: &[Self]) -> Result<(), cxx::Exception> {
|
|
135
|
+
index.inner.add_f32(key, vector)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
impl VectorType for f64 {
|
|
140
|
+
fn search(index: &Index, query: &[Self], count: usize) -> Result<ffi::Matches, cxx::Exception> {
|
|
141
|
+
index.inner.search_f64(query, count)
|
|
142
|
+
}
|
|
143
|
+
fn get(index: &Index, key: u64, vector: &mut [Self]) -> Result<usize, cxx::Exception> {
|
|
144
|
+
index.inner.get_f64(key, vector)
|
|
145
|
+
}
|
|
146
|
+
fn add(index: &Index, key: u64, vector: &[Self]) -> Result<(), cxx::Exception> {
|
|
147
|
+
index.inner.add_f64(key, vector)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
impl Index {
|
|
152
|
+
pub fn new(options: &ffi::IndexOptions) -> Result<Self, cxx::Exception> {
|
|
153
|
+
match ffi::new_native_index(options) {
|
|
154
|
+
Ok(inner) => Result::Ok(Self { inner }),
|
|
155
|
+
Err(err) => Err(err),
|
|
123
156
|
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/// Performs k-Approximate Nearest Neighbors (kANN) Search for closest vectors to the provided query.
|
|
160
|
+
///
|
|
161
|
+
/// # Arguments
|
|
162
|
+
///
|
|
163
|
+
/// * `query` - A slice containing the query vector data.
|
|
164
|
+
/// * `count` - The maximum number of neighbors to search for.
|
|
165
|
+
///
|
|
166
|
+
/// # Returns
|
|
167
|
+
///
|
|
168
|
+
/// A `Result` containing the matches found.
|
|
169
|
+
pub fn search<T: VectorType>(
|
|
170
|
+
self: &Index,
|
|
171
|
+
query: &[T],
|
|
172
|
+
count: usize,
|
|
173
|
+
) -> Result<ffi::Matches, cxx::Exception> {
|
|
174
|
+
T::search(self, query, count)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/// Adds a vector with a specified key to the index.
|
|
178
|
+
///
|
|
179
|
+
/// # Arguments
|
|
180
|
+
///
|
|
181
|
+
/// * `key` - The key associated with the vector.
|
|
182
|
+
/// * `vector` - A slice containing the vector data.
|
|
183
|
+
pub fn add<T: VectorType>(self: &Index, key: u64, vector: &[T]) -> Result<(), cxx::Exception> {
|
|
184
|
+
T::add(self, key, vector)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/// Extracts one or more vectors matching specified key.
|
|
188
|
+
///
|
|
189
|
+
/// # Arguments
|
|
190
|
+
///
|
|
191
|
+
/// * `key` - The key associated with the vector.
|
|
192
|
+
/// * `vector` - A slice containing the vector data.
|
|
193
|
+
pub fn get<T: VectorType>(
|
|
194
|
+
self: &Index,
|
|
195
|
+
key: u64,
|
|
196
|
+
vector: &mut [T],
|
|
197
|
+
) -> Result<usize, cxx::Exception> {
|
|
198
|
+
T::get(self, key, vector)
|
|
199
|
+
}
|
|
124
200
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
///
|
|
134
|
-
/// * `key` - The key of the vector to be removed.
|
|
135
|
-
///
|
|
136
|
-
/// # Returns
|
|
137
|
-
///
|
|
138
|
-
/// `true` if the vector is successfully removed, `false` otherwise.
|
|
139
|
-
pub fn remove(self: &Index, key: u64) -> Result<bool>;
|
|
140
|
-
|
|
141
|
-
/// Renames the vector under a certain key.
|
|
142
|
-
///
|
|
143
|
-
/// # Arguments
|
|
144
|
-
///
|
|
145
|
-
/// * `from` - The key of the vector to be renamed.
|
|
146
|
-
/// * `to` - The new name.
|
|
147
|
-
///
|
|
148
|
-
/// # Returns
|
|
149
|
-
///
|
|
150
|
-
/// `true` if the vector is successfully renamed, `false` otherwise.
|
|
151
|
-
pub fn rename(self: &Index, from: u64, to: u64) -> Result<bool>;
|
|
152
|
-
|
|
153
|
-
/// Checks if the index contains a vector with a specified key.
|
|
154
|
-
///
|
|
155
|
-
/// # Arguments
|
|
156
|
-
///
|
|
157
|
-
/// * `key` - The key to be checked.
|
|
158
|
-
///
|
|
159
|
-
/// # Returns
|
|
160
|
-
///
|
|
161
|
-
/// `true` if the index contains the vector with the given key, `false` otherwise.
|
|
162
|
-
pub fn contains(self: &Index, key: u64) -> bool;
|
|
163
|
-
|
|
164
|
-
/// Saves the index to a specified file.
|
|
165
|
-
///
|
|
166
|
-
/// # Arguments
|
|
167
|
-
///
|
|
168
|
-
/// * `path` - The file path where the index will be saved.
|
|
169
|
-
pub fn save(self: &Index, path: &str) -> Result<()>;
|
|
170
|
-
|
|
171
|
-
/// Loads the index from a specified file.
|
|
172
|
-
///
|
|
173
|
-
/// # Arguments
|
|
174
|
-
///
|
|
175
|
-
/// * `path` - The file path from where the index will be loaded.
|
|
176
|
-
pub fn load(self: &Index, path: &str) -> Result<()>;
|
|
177
|
-
|
|
178
|
-
/// Creates a view of the index from a file without loading it into memory.
|
|
179
|
-
///
|
|
180
|
-
/// # Arguments
|
|
181
|
-
///
|
|
182
|
-
/// * `path` - The file path from where the view will be created.
|
|
183
|
-
pub fn view(self: &Index, path: &str) -> Result<()>;
|
|
184
|
-
|
|
185
|
-
/// Saves the index to a specified file.
|
|
186
|
-
///
|
|
187
|
-
/// # Arguments
|
|
188
|
-
///
|
|
189
|
-
/// * `path` - The file path where the index will be saved.
|
|
190
|
-
pub fn save_to_buffer(self: &Index, buffer: &mut [u8]) -> Result<()>;
|
|
191
|
-
|
|
192
|
-
/// Loads the index from a specified file.
|
|
193
|
-
///
|
|
194
|
-
/// # Arguments
|
|
195
|
-
///
|
|
196
|
-
/// * `path` - The file path from where the index will be loaded.
|
|
197
|
-
pub fn load_from_buffer(self: &Index, buffer: &[u8]) -> Result<()>;
|
|
198
|
-
|
|
199
|
-
/// Creates a view of the index from a file without loading it into memory.
|
|
200
|
-
///
|
|
201
|
-
/// # Arguments
|
|
202
|
-
///
|
|
203
|
-
/// * `path` - The file path from where the view will be created.
|
|
204
|
-
pub fn view_from_buffer(self: &Index, buffer: &[u8]) -> Result<()>;
|
|
201
|
+
/// Reserves memory for a specified number of incoming vectors.
|
|
202
|
+
///
|
|
203
|
+
/// # Arguments
|
|
204
|
+
///
|
|
205
|
+
/// * `capacity` - The desired total capacity including the current size.
|
|
206
|
+
pub fn reserve(self: &Index, capacity: usize) -> Result<(), cxx::Exception> {
|
|
207
|
+
self.inner.reserve(capacity)
|
|
208
|
+
}
|
|
205
209
|
|
|
210
|
+
/// Retrieves the number of dimensions in the vectors indexed.
|
|
211
|
+
pub fn dimensions(self: &Index) -> usize {
|
|
212
|
+
self.inner.dimensions()
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/// Retrieves the connectivity parameter that limits connections-per-node in the graph.
|
|
216
|
+
pub fn connectivity(self: &Index) -> usize {
|
|
217
|
+
self.inner.connectivity()
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/// Retrieves the current number of vectors in the index.
|
|
221
|
+
pub fn size(self: &Index) -> usize {
|
|
222
|
+
self.inner.size()
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/// Retrieves the total capacity of the index, including reserved space.
|
|
226
|
+
pub fn capacity(self: &Index) -> usize {
|
|
227
|
+
self.inner.capacity()
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/// Reports expected file size after serialization.
|
|
231
|
+
pub fn serialized_length(self: &Index) -> usize {
|
|
232
|
+
self.inner.serialized_length()
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/// Removes the vector associated with the given key from the index.
|
|
236
|
+
///
|
|
237
|
+
/// # Arguments
|
|
238
|
+
///
|
|
239
|
+
/// * `key` - The key of the vector to be removed.
|
|
240
|
+
///
|
|
241
|
+
/// # Returns
|
|
242
|
+
///
|
|
243
|
+
/// `true` if the vector is successfully removed, `false` otherwise.
|
|
244
|
+
pub fn remove(self: &Index, key: u64) -> Result<usize, cxx::Exception> {
|
|
245
|
+
self.inner.remove(key)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/// Renames the vector under a certain key.
|
|
249
|
+
///
|
|
250
|
+
/// # Arguments
|
|
251
|
+
///
|
|
252
|
+
/// * `from` - The key of the vector to be renamed.
|
|
253
|
+
/// * `to` - The new name.
|
|
254
|
+
///
|
|
255
|
+
/// # Returns
|
|
256
|
+
///
|
|
257
|
+
/// `true` if the vector is successfully renamed, `false` otherwise.
|
|
258
|
+
pub fn rename(self: &Index, from: u64, to: u64) -> Result<usize, cxx::Exception> {
|
|
259
|
+
self.inner.rename(from, to)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/// Checks if the index contains a vector with a specified key.
|
|
263
|
+
///
|
|
264
|
+
/// # Arguments
|
|
265
|
+
///
|
|
266
|
+
/// * `key` - The key to be checked.
|
|
267
|
+
///
|
|
268
|
+
/// # Returns
|
|
269
|
+
///
|
|
270
|
+
/// `true` if the index contains the vector with the given key, `false` otherwise.
|
|
271
|
+
pub fn contains(self: &Index, key: u64) -> bool {
|
|
272
|
+
self.inner.contains(key)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/// Count the count of vector with the same specified key.
|
|
276
|
+
///
|
|
277
|
+
/// # Arguments
|
|
278
|
+
///
|
|
279
|
+
/// * `key` - The key to be checked.
|
|
280
|
+
///
|
|
281
|
+
/// # Returns
|
|
282
|
+
///
|
|
283
|
+
/// Number of vectors found.
|
|
284
|
+
pub fn count(self: &Index, key: u64) -> usize {
|
|
285
|
+
self.inner.count(key)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/// Saves the index to a specified file.
|
|
289
|
+
///
|
|
290
|
+
/// # Arguments
|
|
291
|
+
///
|
|
292
|
+
/// * `path` - The file path where the index will be saved.
|
|
293
|
+
pub fn save(self: &Index, path: &str) -> Result<(), cxx::Exception> {
|
|
294
|
+
self.inner.save(path)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/// Loads the index from a specified file.
|
|
298
|
+
///
|
|
299
|
+
/// # Arguments
|
|
300
|
+
///
|
|
301
|
+
/// * `path` - The file path from where the index will be loaded.
|
|
302
|
+
pub fn load(self: &Index, path: &str) -> Result<(), cxx::Exception> {
|
|
303
|
+
self.inner.load(path)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/// Creates a view of the index from a file without loading it into memory.
|
|
307
|
+
///
|
|
308
|
+
/// # Arguments
|
|
309
|
+
///
|
|
310
|
+
/// * `path` - The file path from where the view will be created.
|
|
311
|
+
pub fn view(self: &Index, path: &str) -> Result<(), cxx::Exception> {
|
|
312
|
+
self.inner.view(path)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/// Saves the index to a specified file.
|
|
316
|
+
///
|
|
317
|
+
/// # Arguments
|
|
318
|
+
///
|
|
319
|
+
/// * `path` - The file path where the index will be saved.
|
|
320
|
+
pub fn save_to_buffer(self: &Index, buffer: &mut [u8]) -> Result<(), cxx::Exception> {
|
|
321
|
+
self.inner.save_to_buffer(buffer)
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/// Loads the index from a specified file.
|
|
325
|
+
///
|
|
326
|
+
/// # Arguments
|
|
327
|
+
///
|
|
328
|
+
/// * `path` - The file path from where the index will be loaded.
|
|
329
|
+
pub fn load_from_buffer(self: &Index, buffer: &[u8]) -> Result<(), cxx::Exception> {
|
|
330
|
+
self.inner.load_from_buffer(buffer)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/// Creates a view of the index from a file without loading it into memory.
|
|
334
|
+
///
|
|
335
|
+
/// # Arguments
|
|
336
|
+
///
|
|
337
|
+
/// * `path` - The file path from where the view will be created.
|
|
338
|
+
pub fn view_from_buffer(self: &Index, buffer: &[u8]) -> Result<(), cxx::Exception> {
|
|
339
|
+
self.inner.view_from_buffer(buffer)
|
|
206
340
|
}
|
|
207
341
|
}
|
|
208
342
|
|
|
343
|
+
pub fn new_index(options: &ffi::IndexOptions) -> Result<Index, cxx::Exception> {
|
|
344
|
+
Index::new(options)
|
|
345
|
+
}
|
|
346
|
+
|
|
209
347
|
#[cfg(test)]
|
|
210
348
|
mod tests {
|
|
211
|
-
use crate::ffi::new_index;
|
|
212
349
|
use crate::ffi::IndexOptions;
|
|
213
350
|
use crate::ffi::MetricKind;
|
|
214
|
-
|
|
351
|
+
|
|
352
|
+
use crate::new_index;
|
|
353
|
+
use crate::Index;
|
|
215
354
|
|
|
216
355
|
#[test]
|
|
217
356
|
fn integration() {
|
|
218
|
-
let mut options = IndexOptions
|
|
219
|
-
|
|
220
|
-
metric: MetricKind::IP,
|
|
221
|
-
quantization: ScalarKind::F16,
|
|
222
|
-
connectivity: 0,
|
|
223
|
-
expansion_add: 0,
|
|
224
|
-
expansion_search: 0,
|
|
225
|
-
};
|
|
357
|
+
let mut options = IndexOptions::default();
|
|
358
|
+
options.dimensions = 5;
|
|
226
359
|
|
|
227
|
-
let index =
|
|
360
|
+
let index = Index::new(&options).unwrap();
|
|
228
361
|
|
|
229
362
|
assert!(index.reserve(10).is_ok());
|
|
230
363
|
assert!(index.capacity() >= 10);
|
package/setup.py
CHANGED
|
@@ -12,8 +12,11 @@ macros_args = []
|
|
|
12
12
|
if sys.platform == "linux":
|
|
13
13
|
compile_args.append("-std=c++17")
|
|
14
14
|
compile_args.append("-O3") # Maximize performance
|
|
15
|
-
compile_args.append("-g") # Simplify debugging
|
|
16
15
|
compile_args.append("-Wno-unknown-pragmas")
|
|
16
|
+
compile_args.append("-fdiagnostics-color=always")
|
|
17
|
+
|
|
18
|
+
# Simplify debugging, but the normal `-g` may make builds much longer!
|
|
19
|
+
compile_args.append("-g1")
|
|
17
20
|
|
|
18
21
|
macros_args.append(("USEARCH_USE_NATIVE_F16", "0"))
|
|
19
22
|
macros_args.append(("USEARCH_USE_SIMSIMD", "1"))
|
|
@@ -27,9 +30,12 @@ if sys.platform == "darwin":
|
|
|
27
30
|
compile_args.append("-mmacosx-version-min=10.15")
|
|
28
31
|
compile_args.append("-std=c++17")
|
|
29
32
|
compile_args.append("-O3") # Maximize performance
|
|
30
|
-
compile_args.append("-
|
|
33
|
+
compile_args.append("-fcolor-diagnostics")
|
|
31
34
|
compile_args.append("-Wno-unknown-pragmas")
|
|
32
35
|
|
|
36
|
+
# Simplify debugging, but the normal `-g` may make builds much longer!
|
|
37
|
+
compile_args.append("-g1")
|
|
38
|
+
|
|
33
39
|
# Linking OpenMP requires additional preparation in CIBuildWheel
|
|
34
40
|
# macros_args.append(("USEARCH_USE_OPENMP", "1"))
|
|
35
41
|
# compile_args.append("-Xpreprocessor -fopenmp")
|