usearch 2.2.1 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.toml +1 -1
- package/conanfile.py +1 -1
- package/cpp/bench.cpp +1 -0
- package/include/usearch/index.hpp +48 -12
- package/include/usearch/index_dense.hpp +43 -20
- package/package.json +1 -1
- package/python/lib.cpp +133 -61
- package/python/usearch/index.py +112 -79
- package/wasmer.toml +1 -1
package/Cargo.toml
CHANGED
package/conanfile.py
CHANGED
|
@@ -4,7 +4,7 @@ from conans import ConanFile
|
|
|
4
4
|
class USearchConan(ConanFile):
|
|
5
5
|
|
|
6
6
|
name = 'USearch'
|
|
7
|
-
version = '2.
|
|
7
|
+
version = '2.3.0'
|
|
8
8
|
license = 'Apache 2.0'
|
|
9
9
|
url = 'https://github.com/unum-cloud/usearch'
|
|
10
10
|
description = 'Smaller & Faster Single-File Vector Search Engine from Unum'
|
package/cpp/bench.cpp
CHANGED
|
@@ -372,6 +372,7 @@ static void single_shot(dataset_at& dataset, index_at& index, bool construct = t
|
|
|
372
372
|
executor, [&](std::size_t progress, std::size_t total) {
|
|
373
373
|
if (progress % 1000 == 0)
|
|
374
374
|
printer.print(progress, total);
|
|
375
|
+
return true;
|
|
375
376
|
});
|
|
376
377
|
join_attempts = result.visited_members;
|
|
377
378
|
}
|
|
@@ -1212,9 +1212,10 @@ struct dummy_callback_t {
|
|
|
1212
1212
|
*
|
|
1213
1213
|
* This is particularly helpful when handling long-running tasks, like serialization,
|
|
1214
1214
|
* saving, and loading from disk, or index-level joins.
|
|
1215
|
+
* The reporter checks return value to continue or stop the process, `false` means need to stop.
|
|
1215
1216
|
*/
|
|
1216
1217
|
struct dummy_progress_t {
|
|
1217
|
-
inline
|
|
1218
|
+
inline bool operator()(std::size_t /*processed*/, std::size_t /*total*/) const noexcept { return true; }
|
|
1218
1219
|
};
|
|
1219
1220
|
|
|
1220
1221
|
/**
|
|
@@ -2690,6 +2691,10 @@ class index_gt {
|
|
|
2690
2691
|
if (!output(&header, sizeof(header)))
|
|
2691
2692
|
return result.failed("Failed to serialize the header into stream");
|
|
2692
2693
|
|
|
2694
|
+
// Progress status
|
|
2695
|
+
std::size_t processed = 0;
|
|
2696
|
+
std::size_t const total = 2 * header.size;
|
|
2697
|
+
|
|
2693
2698
|
// Export the number of levels per node
|
|
2694
2699
|
// That is both enough to estimate the overall memory consumption,
|
|
2695
2700
|
// and to be able to estimate the offsets of every entry in the file.
|
|
@@ -2697,15 +2702,18 @@ class index_gt {
|
|
|
2697
2702
|
node_t node = node_at_(i);
|
|
2698
2703
|
level_t level = node.level();
|
|
2699
2704
|
if (!output(&level, sizeof(level)))
|
|
2700
|
-
return result.failed("Failed to serialize
|
|
2705
|
+
return result.failed("Failed to serialize into stream");
|
|
2706
|
+
if (!progress(++processed, total))
|
|
2707
|
+
return result.failed("Terminated by user");
|
|
2701
2708
|
}
|
|
2702
2709
|
|
|
2703
2710
|
// After that dump the nodes themselves
|
|
2704
2711
|
for (std::size_t i = 0; i != header.size; ++i) {
|
|
2705
2712
|
span_bytes_t node_bytes = node_bytes_(node_at_(i));
|
|
2706
2713
|
if (!output(node_bytes.data(), node_bytes.size()))
|
|
2707
|
-
return result.failed("Failed to serialize
|
|
2708
|
-
progress(
|
|
2714
|
+
return result.failed("Failed to serialize into stream");
|
|
2715
|
+
if (!progress(++processed, total))
|
|
2716
|
+
return result.failed("Terminated by user");
|
|
2709
2717
|
}
|
|
2710
2718
|
|
|
2711
2719
|
return {};
|
|
@@ -2763,7 +2771,8 @@ class index_gt {
|
|
|
2763
2771
|
return result.failed("Failed to pull nodes from the stream");
|
|
2764
2772
|
}
|
|
2765
2773
|
nodes_[i] = node_t{node_bytes.data()};
|
|
2766
|
-
progress(i, header.size)
|
|
2774
|
+
if (!progress(i + 1, header.size))
|
|
2775
|
+
return result.failed("Terminated by user");
|
|
2767
2776
|
}
|
|
2768
2777
|
return {};
|
|
2769
2778
|
}
|
|
@@ -2936,7 +2945,8 @@ class index_gt {
|
|
|
2936
2945
|
// Rapidly address all the nodes
|
|
2937
2946
|
for (std::size_t i = 0; i != header.size; ++i) {
|
|
2938
2947
|
nodes_[i] = node_t{(byte_t*)file.data() + offsets[i]};
|
|
2939
|
-
progress(i, header.size)
|
|
2948
|
+
if (!progress(i + 1, header.size))
|
|
2949
|
+
return result.failed("Terminated by user");
|
|
2940
2950
|
}
|
|
2941
2951
|
viewed_file_ = std::move(file);
|
|
2942
2952
|
return {};
|
|
@@ -2986,8 +2996,13 @@ class index_gt {
|
|
|
2986
2996
|
using slot_level_allocator_t = typename dynamic_allocator_traits_t::template rebind_alloc<slot_level_t>;
|
|
2987
2997
|
buffer_gt<slot_level_t, slot_level_allocator_t> slots_and_levels(size());
|
|
2988
2998
|
|
|
2999
|
+
// Progress status
|
|
3000
|
+
std::atomic<bool> do_tasks{true};
|
|
3001
|
+
std::atomic<std::size_t> processed{0};
|
|
3002
|
+
std::size_t const total = 3 * slots_and_levels.size();
|
|
3003
|
+
|
|
2989
3004
|
// For every bottom level node, determine its parent cluster
|
|
2990
|
-
executor.
|
|
3005
|
+
executor.dynamic(slots_and_levels.size(), [&](std::size_t thread_idx, std::size_t old_slot) {
|
|
2991
3006
|
context_t& context = contexts_[thread_idx];
|
|
2992
3007
|
std::size_t cluster = search_for_one_( //
|
|
2993
3008
|
values[citerator_at(old_slot)], //
|
|
@@ -2997,7 +3012,13 @@ class index_gt {
|
|
|
2997
3012
|
static_cast<compressed_slot_t>(old_slot), //
|
|
2998
3013
|
static_cast<compressed_slot_t>(cluster), //
|
|
2999
3014
|
node_at_(old_slot).level()};
|
|
3015
|
+
++processed;
|
|
3016
|
+
if (thread_idx == 0)
|
|
3017
|
+
do_tasks = progress(processed.load(), total);
|
|
3018
|
+
return do_tasks.load();
|
|
3000
3019
|
});
|
|
3020
|
+
if (!do_tasks.load())
|
|
3021
|
+
return;
|
|
3001
3022
|
|
|
3002
3023
|
// Where the actual permutation happens:
|
|
3003
3024
|
std::sort(slots_and_levels.begin(), slots_and_levels.end(), [](slot_level_t const& a, slot_level_t const& b) {
|
|
@@ -3027,8 +3048,8 @@ class index_gt {
|
|
|
3027
3048
|
neighbor = static_cast<compressed_slot_t>(old_slot_to_new[compressed_slot_t(neighbor)]);
|
|
3028
3049
|
|
|
3029
3050
|
reordered_nodes[new_slot] = new_node;
|
|
3030
|
-
|
|
3031
|
-
|
|
3051
|
+
if (!progress(++processed, total))
|
|
3052
|
+
return;
|
|
3032
3053
|
}
|
|
3033
3054
|
|
|
3034
3055
|
for (std::size_t new_slot = 0; new_slot != slots_and_levels.size(); ++new_slot) {
|
|
@@ -3036,6 +3057,8 @@ class index_gt {
|
|
|
3036
3057
|
slot_transition(node_at_(old_slot).ckey(), //
|
|
3037
3058
|
static_cast<compressed_slot_t>(old_slot), //
|
|
3038
3059
|
static_cast<compressed_slot_t>(new_slot));
|
|
3060
|
+
if (!progress(++processed, total))
|
|
3061
|
+
return;
|
|
3039
3062
|
}
|
|
3040
3063
|
|
|
3041
3064
|
nodes_ = std::move(reordered_nodes);
|
|
@@ -3064,9 +3087,13 @@ class index_gt {
|
|
|
3064
3087
|
executor_at&& executor = executor_at{}, //
|
|
3065
3088
|
progress_at&& progress = progress_at{}) noexcept {
|
|
3066
3089
|
|
|
3090
|
+
// Progress status
|
|
3091
|
+
std::atomic<bool> do_tasks{true};
|
|
3092
|
+
std::atomic<std::size_t> processed{0};
|
|
3093
|
+
|
|
3067
3094
|
// Erase all the incoming links
|
|
3068
3095
|
std::size_t nodes_count = size();
|
|
3069
|
-
executor.
|
|
3096
|
+
executor.dynamic(nodes_count, [&](std::size_t thread_idx, std::size_t node_idx) {
|
|
3070
3097
|
node_t node = node_at_(node_idx);
|
|
3071
3098
|
for (level_t level = 0; level <= node.level(); ++level) {
|
|
3072
3099
|
neighbors_ref_t neighbors = neighbors_(node, level);
|
|
@@ -3079,8 +3106,14 @@ class index_gt {
|
|
|
3079
3106
|
neighbors.push_back(neighbor_slot);
|
|
3080
3107
|
}
|
|
3081
3108
|
}
|
|
3082
|
-
|
|
3109
|
+
++processed;
|
|
3110
|
+
if (thread_idx == 0)
|
|
3111
|
+
do_tasks = progress(processed.load(), nodes_count);
|
|
3112
|
+
return do_tasks.load();
|
|
3083
3113
|
});
|
|
3114
|
+
|
|
3115
|
+
// At the end report the latest numbers, because the reporter thread may be finished earlier
|
|
3116
|
+
progress(processed.load(), nodes_count);
|
|
3084
3117
|
}
|
|
3085
3118
|
|
|
3086
3119
|
private:
|
|
@@ -3710,7 +3743,10 @@ static join_result_t join( //
|
|
|
3710
3743
|
passed_rounds = ++rounds;
|
|
3711
3744
|
total_rounds = passed_rounds + free_men.size();
|
|
3712
3745
|
}
|
|
3713
|
-
progress(passed_rounds, total_rounds)
|
|
3746
|
+
if (thread_idx == 0 && !progress(passed_rounds, total_rounds)) {
|
|
3747
|
+
atomic_error.store("Terminated by user");
|
|
3748
|
+
break;
|
|
3749
|
+
}
|
|
3714
3750
|
while (men_locks.atomic_set(free_man_slot))
|
|
3715
3751
|
;
|
|
3716
3752
|
|
|
@@ -767,7 +767,9 @@ class index_dense_gt {
|
|
|
767
767
|
* @brief Saves serialized binary index representation to a stream.
|
|
768
768
|
*/
|
|
769
769
|
template <typename output_callback_at, typename progress_at = dummy_progress_t>
|
|
770
|
-
serialization_result_t save_to_stream(output_callback_at&& output,
|
|
770
|
+
serialization_result_t save_to_stream(output_callback_at&& output, //
|
|
771
|
+
serialization_config_t config = {}, //
|
|
772
|
+
progress_at&& progress = {}) const {
|
|
771
773
|
|
|
772
774
|
serialization_result_t result;
|
|
773
775
|
std::uint64_t matrix_rows = 0;
|
|
@@ -831,7 +833,7 @@ class index_dense_gt {
|
|
|
831
833
|
}
|
|
832
834
|
|
|
833
835
|
// Save the actual proximity graph
|
|
834
|
-
return typed_->save_to_stream(std::forward<output_callback_at>(output));
|
|
836
|
+
return typed_->save_to_stream(std::forward<output_callback_at>(output), std::forward<progress_at>(progress));
|
|
835
837
|
}
|
|
836
838
|
|
|
837
839
|
/**
|
|
@@ -853,8 +855,10 @@ class index_dense_gt {
|
|
|
853
855
|
* @param[in] config Configuration parameters for imports.
|
|
854
856
|
* @return Outcome descriptor explicitly convertible to boolean.
|
|
855
857
|
*/
|
|
856
|
-
template <typename input_callback_at>
|
|
857
|
-
serialization_result_t load_from_stream(input_callback_at&& input,
|
|
858
|
+
template <typename input_callback_at, typename progress_at = dummy_progress_t>
|
|
859
|
+
serialization_result_t load_from_stream(input_callback_at&& input, //
|
|
860
|
+
serialization_config_t config = {}, //
|
|
861
|
+
progress_at&& progress = {}) {
|
|
858
862
|
|
|
859
863
|
// Discard all previous memory allocations of `vectors_tape_allocator_`
|
|
860
864
|
reset();
|
|
@@ -915,7 +919,7 @@ class index_dense_gt {
|
|
|
915
919
|
}
|
|
916
920
|
|
|
917
921
|
// Pull the actual proximity graph
|
|
918
|
-
result = typed_->load_from_stream(std::forward<input_callback_at>(input));
|
|
922
|
+
result = typed_->load_from_stream(std::forward<input_callback_at>(input), std::forward<progress_at>(progress));
|
|
919
923
|
if (!result)
|
|
920
924
|
return result;
|
|
921
925
|
if (typed_->size() != static_cast<std::size_t>(matrix_rows))
|
|
@@ -931,7 +935,10 @@ class index_dense_gt {
|
|
|
931
935
|
* @param[in] config Configuration parameters for imports.
|
|
932
936
|
* @return Outcome descriptor explicitly convertible to boolean.
|
|
933
937
|
*/
|
|
934
|
-
|
|
938
|
+
template <typename progress_at = dummy_progress_t>
|
|
939
|
+
serialization_result_t view(memory_mapped_file_t file, //
|
|
940
|
+
std::size_t offset = 0, serialization_config_t config = {}, //
|
|
941
|
+
progress_at&& progress = {}) {
|
|
935
942
|
|
|
936
943
|
// Discard all previous memory allocations of `vectors_tape_allocator_`
|
|
937
944
|
reset();
|
|
@@ -997,7 +1004,7 @@ class index_dense_gt {
|
|
|
997
1004
|
}
|
|
998
1005
|
|
|
999
1006
|
// Pull the actual proximity graph
|
|
1000
|
-
result = typed_->view(std::move(file), offset);
|
|
1007
|
+
result = typed_->view(std::move(file), offset, std::forward<progress_at>(progress));
|
|
1001
1008
|
if (!result)
|
|
1002
1009
|
return result;
|
|
1003
1010
|
if (typed_->size() != static_cast<std::size_t>(matrix_rows))
|
|
@@ -1019,7 +1026,9 @@ class index_dense_gt {
|
|
|
1019
1026
|
* @param[in] config Configuration parameters for exports.
|
|
1020
1027
|
* @return Outcome descriptor explicitly convertible to boolean.
|
|
1021
1028
|
*/
|
|
1022
|
-
|
|
1029
|
+
template <typename progress_at = dummy_progress_t>
|
|
1030
|
+
serialization_result_t save(output_file_t file, serialization_config_t config = {},
|
|
1031
|
+
progress_at&& progress = {}) const {
|
|
1023
1032
|
|
|
1024
1033
|
serialization_result_t io_result = file.open_if_not();
|
|
1025
1034
|
if (!io_result)
|
|
@@ -1030,7 +1039,7 @@ class index_dense_gt {
|
|
|
1030
1039
|
io_result = file.write(buffer, length);
|
|
1031
1040
|
return !!io_result;
|
|
1032
1041
|
},
|
|
1033
|
-
config);
|
|
1042
|
+
config, std::forward<progress_at>(progress));
|
|
1034
1043
|
|
|
1035
1044
|
if (!stream_result)
|
|
1036
1045
|
return stream_result;
|
|
@@ -1041,8 +1050,11 @@ class index_dense_gt {
|
|
|
1041
1050
|
* @brief Memory-maps the serialized binary index representation from disk,
|
|
1042
1051
|
* @b without copying data into RAM, and fetching it on-demand.
|
|
1043
1052
|
*/
|
|
1044
|
-
|
|
1045
|
-
|
|
1053
|
+
template <typename progress_at = dummy_progress_t>
|
|
1054
|
+
serialization_result_t save(memory_mapped_file_t file, //
|
|
1055
|
+
std::size_t offset = 0, //
|
|
1056
|
+
serialization_config_t config = {}, //
|
|
1057
|
+
progress_at&& progress = {}) const {
|
|
1046
1058
|
|
|
1047
1059
|
serialization_result_t io_result = file.open_if_not();
|
|
1048
1060
|
if (!io_result)
|
|
@@ -1056,7 +1068,7 @@ class index_dense_gt {
|
|
|
1056
1068
|
offset += length;
|
|
1057
1069
|
return true;
|
|
1058
1070
|
},
|
|
1059
|
-
config);
|
|
1071
|
+
config, std::forward<progress_at>(progress));
|
|
1060
1072
|
|
|
1061
1073
|
return stream_result;
|
|
1062
1074
|
}
|
|
@@ -1067,7 +1079,8 @@ class index_dense_gt {
|
|
|
1067
1079
|
* @param[in] config Configuration parameters for imports.
|
|
1068
1080
|
* @return Outcome descriptor explicitly convertible to boolean.
|
|
1069
1081
|
*/
|
|
1070
|
-
|
|
1082
|
+
template <typename progress_at = dummy_progress_t>
|
|
1083
|
+
serialization_result_t load(input_file_t file, serialization_config_t config = {}, progress_at&& progress = {}) {
|
|
1071
1084
|
|
|
1072
1085
|
serialization_result_t io_result = file.open_if_not();
|
|
1073
1086
|
if (!io_result)
|
|
@@ -1078,7 +1091,7 @@ class index_dense_gt {
|
|
|
1078
1091
|
io_result = file.read(buffer, length);
|
|
1079
1092
|
return !!io_result;
|
|
1080
1093
|
},
|
|
1081
|
-
config);
|
|
1094
|
+
config, std::forward<progress_at>(progress));
|
|
1082
1095
|
|
|
1083
1096
|
if (!stream_result)
|
|
1084
1097
|
return stream_result;
|
|
@@ -1089,7 +1102,11 @@ class index_dense_gt {
|
|
|
1089
1102
|
* @brief Memory-maps the serialized binary index representation from disk,
|
|
1090
1103
|
* @b without copying data into RAM, and fetching it on-demand.
|
|
1091
1104
|
*/
|
|
1092
|
-
|
|
1105
|
+
template <typename progress_at = dummy_progress_t>
|
|
1106
|
+
serialization_result_t load(memory_mapped_file_t file, //
|
|
1107
|
+
std::size_t offset = 0, //
|
|
1108
|
+
serialization_config_t config = {}, //
|
|
1109
|
+
progress_at&& progress = {}) {
|
|
1093
1110
|
|
|
1094
1111
|
serialization_result_t io_result = file.open_if_not();
|
|
1095
1112
|
if (!io_result)
|
|
@@ -1103,17 +1120,23 @@ class index_dense_gt {
|
|
|
1103
1120
|
offset += length;
|
|
1104
1121
|
return true;
|
|
1105
1122
|
},
|
|
1106
|
-
config);
|
|
1123
|
+
config, std::forward<progress_at>(progress));
|
|
1107
1124
|
|
|
1108
1125
|
return stream_result;
|
|
1109
1126
|
}
|
|
1110
1127
|
|
|
1111
|
-
|
|
1112
|
-
|
|
1128
|
+
template <typename progress_at = dummy_progress_t>
|
|
1129
|
+
serialization_result_t save(char const* file_path, //
|
|
1130
|
+
serialization_config_t config = {}, //
|
|
1131
|
+
progress_at&& progress = {}) const {
|
|
1132
|
+
return save(output_file_t(file_path), config, std::forward<progress_at>(progress));
|
|
1113
1133
|
}
|
|
1114
1134
|
|
|
1115
|
-
|
|
1116
|
-
|
|
1135
|
+
template <typename progress_at = dummy_progress_t>
|
|
1136
|
+
serialization_result_t load(char const* file_path, //
|
|
1137
|
+
serialization_config_t config = {}, //
|
|
1138
|
+
progress_at&& progress = {}) {
|
|
1139
|
+
return load(input_file_t(file_path), config, std::forward<progress_at>(progress));
|
|
1117
1140
|
}
|
|
1118
1141
|
|
|
1119
1142
|
/**
|
package/package.json
CHANGED
package/python/lib.cpp
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
#define _CRT_SECURE_NO_WARNINGS
|
|
23
23
|
#define PY_SSIZE_T_CLEAN
|
|
24
|
+
#include <pybind11/functional.h>
|
|
24
25
|
#include <pybind11/numpy.h>
|
|
25
26
|
#include <pybind11/pybind11.h>
|
|
26
27
|
#include <pybind11/stl.h>
|
|
@@ -55,6 +56,18 @@ using dense_labeling_result_t = typename index_dense_t::labeling_result_t;
|
|
|
55
56
|
using dense_cluster_result_t = typename index_dense_t::cluster_result_t;
|
|
56
57
|
using dense_clustering_result_t = typename index_dense_t::clustering_result_t;
|
|
57
58
|
|
|
59
|
+
using progress_func_t = std::function<bool(std::size_t /*processed*/, std::size_t /*total*/)>;
|
|
60
|
+
|
|
61
|
+
struct progress_t {
|
|
62
|
+
inline progress_t(progress_func_t const& func) : func_(func ? func : &dummy_progress) {}
|
|
63
|
+
inline bool operator()(std::size_t processed, std::size_t total) const noexcept { return func_(processed, total); }
|
|
64
|
+
|
|
65
|
+
private:
|
|
66
|
+
static inline bool dummy_progress(std::size_t /*processed*/, std::size_t /*total*/) { return true; }
|
|
67
|
+
|
|
68
|
+
progress_func_t func_;
|
|
69
|
+
};
|
|
70
|
+
|
|
58
71
|
struct dense_index_py_t : public index_dense_t {
|
|
59
72
|
using native_t = index_dense_t;
|
|
60
73
|
using native_t::add;
|
|
@@ -208,13 +221,18 @@ template <typename scalar_at>
|
|
|
208
221
|
static void add_typed_to_index( //
|
|
209
222
|
dense_index_py_t& index, //
|
|
210
223
|
py::buffer_info const& keys_info, py::buffer_info const& vectors_info, //
|
|
211
|
-
bool force_copy, std::size_t threads
|
|
224
|
+
bool force_copy, std::size_t threads, //
|
|
225
|
+
progress_func_t const& progress) {
|
|
212
226
|
|
|
213
227
|
Py_ssize_t vectors_count = vectors_info.shape[0];
|
|
214
228
|
byte_t const* vectors_data = reinterpret_cast<byte_t const*>(vectors_info.ptr);
|
|
215
229
|
byte_t const* keys_data = reinterpret_cast<byte_t const*>(keys_info.ptr);
|
|
216
230
|
atomic_error_t atomic_error{nullptr};
|
|
217
231
|
|
|
232
|
+
// Progress status
|
|
233
|
+
progress_t progress_{progress};
|
|
234
|
+
std::atomic<std::size_t> processed{0};
|
|
235
|
+
|
|
218
236
|
executor_default_t{threads}.dynamic(vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
219
237
|
dense_key_t key = *reinterpret_cast<dense_key_t const*>(keys_data + task_idx * keys_info.strides[0]);
|
|
220
238
|
scalar_at const* vector = reinterpret_cast<scalar_at const*>(vectors_data + task_idx * vectors_info.strides[0]);
|
|
@@ -225,12 +243,18 @@ static void add_typed_to_index( //
|
|
|
225
243
|
}
|
|
226
244
|
|
|
227
245
|
// We don't want to check for signals from multiple threads
|
|
246
|
+
++processed;
|
|
228
247
|
if (thread_idx == 0)
|
|
229
|
-
if (PyErr_CheckSignals() != 0)
|
|
248
|
+
if (PyErr_CheckSignals() != 0 || !progress_(processed.load(), vectors_count)) {
|
|
249
|
+
atomic_error.store("Operation has been terminated");
|
|
230
250
|
return false;
|
|
251
|
+
}
|
|
231
252
|
return true;
|
|
232
253
|
});
|
|
233
254
|
|
|
255
|
+
// At the end report the latest numbers, because the reporter thread may be finished earlier
|
|
256
|
+
progress_(processed.load(), vectors_count);
|
|
257
|
+
|
|
234
258
|
// Raise the error from a single thread
|
|
235
259
|
auto error = atomic_error.load();
|
|
236
260
|
if (error) {
|
|
@@ -242,7 +266,8 @@ static void add_typed_to_index( //
|
|
|
242
266
|
template <typename index_at>
|
|
243
267
|
static void add_many_to_index( //
|
|
244
268
|
index_at& index, py::buffer keys, py::buffer vectors, //
|
|
245
|
-
bool force_copy, std::size_t threads
|
|
269
|
+
bool force_copy, std::size_t threads, //
|
|
270
|
+
progress_func_t const& progress) {
|
|
246
271
|
|
|
247
272
|
py::buffer_info keys_info = keys.request();
|
|
248
273
|
py::buffer_info vectors_info = vectors.request();
|
|
@@ -272,11 +297,11 @@ static void add_many_to_index( //
|
|
|
272
297
|
|
|
273
298
|
// clang-format off
|
|
274
299
|
switch (numpy_string_to_kind(vectors_info.format)) {
|
|
275
|
-
case scalar_kind_t::b1x8_k: add_typed_to_index<b1x8_t>(index, keys_info, vectors_info, force_copy, threads); break;
|
|
276
|
-
case scalar_kind_t::i8_k: add_typed_to_index<i8_t>(index, keys_info, vectors_info, force_copy, threads); break;
|
|
277
|
-
case scalar_kind_t::f16_k: add_typed_to_index<f16_t>(index, keys_info, vectors_info, force_copy, threads); break;
|
|
278
|
-
case scalar_kind_t::f32_k: add_typed_to_index<f32_t>(index, keys_info, vectors_info, force_copy, threads); break;
|
|
279
|
-
case scalar_kind_t::f64_k: add_typed_to_index<f64_t>(index, keys_info, vectors_info, force_copy, threads); break;
|
|
300
|
+
case scalar_kind_t::b1x8_k: add_typed_to_index<b1x8_t>(index, keys_info, vectors_info, force_copy, threads, progress); break;
|
|
301
|
+
case scalar_kind_t::i8_k: add_typed_to_index<i8_t>(index, keys_info, vectors_info, force_copy, threads, progress); break;
|
|
302
|
+
case scalar_kind_t::f16_k: add_typed_to_index<f16_t>(index, keys_info, vectors_info, force_copy, threads, progress); break;
|
|
303
|
+
case scalar_kind_t::f32_k: add_typed_to_index<f32_t>(index, keys_info, vectors_info, force_copy, threads, progress); break;
|
|
304
|
+
case scalar_kind_t::f64_k: add_typed_to_index<f64_t>(index, keys_info, vectors_info, force_copy, threads, progress); break;
|
|
280
305
|
default: throw std::invalid_argument("Incompatible scalars in the vectors matrix: " + vectors_info.format);
|
|
281
306
|
}
|
|
282
307
|
// clang-format on
|
|
@@ -287,7 +312,8 @@ static void search_typed( //
|
|
|
287
312
|
dense_index_py_t& index, py::buffer_info& vectors_info, //
|
|
288
313
|
std::size_t wanted, bool exact, std::size_t threads, //
|
|
289
314
|
py::array_t<dense_key_t>& keys_py, py::array_t<distance_t>& distances_py, py::array_t<Py_ssize_t>& counts_py,
|
|
290
|
-
std::atomic<std::size_t>& stats_visited_members, std::atomic<std::size_t>& stats_computed_distances
|
|
315
|
+
std::atomic<std::size_t>& stats_visited_members, std::atomic<std::size_t>& stats_computed_distances,
|
|
316
|
+
progress_func_t const& progress) {
|
|
291
317
|
|
|
292
318
|
auto keys_py2d = keys_py.template mutable_unchecked<2>();
|
|
293
319
|
auto distances_py2d = distances_py.template mutable_unchecked<2>();
|
|
@@ -301,6 +327,10 @@ static void search_typed( //
|
|
|
301
327
|
if (!index.reserve(index_limits_t(index.size(), threads)))
|
|
302
328
|
throw std::invalid_argument("Out of memory!");
|
|
303
329
|
|
|
330
|
+
// Progress status
|
|
331
|
+
progress_t progress_{progress};
|
|
332
|
+
std::atomic<std::size_t> processed{0};
|
|
333
|
+
|
|
304
334
|
atomic_error_t atomic_error{nullptr};
|
|
305
335
|
executor_default_t{threads}.dynamic(vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
306
336
|
scalar_at const* vector = (scalar_at const*)(vectors_data + task_idx * vectors_info.strides[0]);
|
|
@@ -317,12 +347,18 @@ static void search_typed( //
|
|
|
317
347
|
stats_computed_distances += result.computed_distances;
|
|
318
348
|
|
|
319
349
|
// We don't want to check for signals from multiple threads
|
|
350
|
+
++processed;
|
|
320
351
|
if (thread_idx == 0)
|
|
321
|
-
if (PyErr_CheckSignals() != 0)
|
|
352
|
+
if (PyErr_CheckSignals() != 0 || !progress_(processed.load(), vectors_count)) {
|
|
353
|
+
atomic_error.store("Operation has been terminated");
|
|
322
354
|
return false;
|
|
355
|
+
}
|
|
323
356
|
return true;
|
|
324
357
|
});
|
|
325
358
|
|
|
359
|
+
// At the end report the latest numbers, because the reporter thread may be finished earlier
|
|
360
|
+
progress_(processed.load(), vectors_count);
|
|
361
|
+
|
|
326
362
|
// Raise the error from a single thread
|
|
327
363
|
auto error = atomic_error.load();
|
|
328
364
|
if (error) {
|
|
@@ -336,7 +372,8 @@ static void search_typed( //
|
|
|
336
372
|
dense_indexes_py_t& indexes, py::buffer_info& vectors_info, //
|
|
337
373
|
std::size_t wanted, bool exact, std::size_t threads, //
|
|
338
374
|
py::array_t<dense_key_t>& keys_py, py::array_t<distance_t>& distances_py, py::array_t<Py_ssize_t>& counts_py,
|
|
339
|
-
std::atomic<std::size_t>& stats_visited_members, std::atomic<std::size_t>& stats_computed_distances
|
|
375
|
+
std::atomic<std::size_t>& stats_visited_members, std::atomic<std::size_t>& stats_computed_distances,
|
|
376
|
+
progress_func_t const& progress) {
|
|
340
377
|
|
|
341
378
|
auto keys_py2d = keys_py.template mutable_unchecked<2>();
|
|
342
379
|
auto distances_py2d = distances_py.template mutable_unchecked<2>();
|
|
@@ -354,6 +391,10 @@ static void search_typed( //
|
|
|
354
391
|
if (!query_mutexes)
|
|
355
392
|
throw std::bad_alloc();
|
|
356
393
|
|
|
394
|
+
// Progress status
|
|
395
|
+
progress_t progress_{progress};
|
|
396
|
+
std::atomic<std::size_t> processed{0};
|
|
397
|
+
|
|
357
398
|
atomic_error_t atomic_error{nullptr};
|
|
358
399
|
executor_default_t{threads}.dynamic(indexes.shards_.size(), [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
359
400
|
dense_index_py_t& index = *indexes.shards_[task_idx].get();
|
|
@@ -388,13 +429,19 @@ static void search_typed( //
|
|
|
388
429
|
stats_computed_distances += result.computed_distances;
|
|
389
430
|
|
|
390
431
|
// We don't want to check for signals from multiple threads
|
|
432
|
+
++processed;
|
|
391
433
|
if (thread_idx == 0)
|
|
392
|
-
if (PyErr_CheckSignals() != 0)
|
|
434
|
+
if (PyErr_CheckSignals() != 0 || !progress_(processed.load(), indexes.shards_.size())) {
|
|
435
|
+
atomic_error.store("Operation has been terminated");
|
|
393
436
|
return false;
|
|
437
|
+
}
|
|
394
438
|
return true;
|
|
395
439
|
}
|
|
396
440
|
});
|
|
397
441
|
|
|
442
|
+
// At the end report the latest numbers, because the reporter thread may be finished earlier
|
|
443
|
+
progress_(processed.load(), indexes.shards_.size());
|
|
444
|
+
|
|
398
445
|
// Raise the error from a single thread
|
|
399
446
|
auto error = atomic_error.load();
|
|
400
447
|
if (error) {
|
|
@@ -416,7 +463,8 @@ static void search_typed( //
|
|
|
416
463
|
*/
|
|
417
464
|
template <typename index_at>
|
|
418
465
|
static py::tuple search_many_in_index( //
|
|
419
|
-
index_at& index, py::buffer vectors, std::size_t wanted, bool exact, std::size_t threads
|
|
466
|
+
index_at& index, py::buffer vectors, std::size_t wanted, bool exact, std::size_t threads,
|
|
467
|
+
progress_func_t const& progress) {
|
|
420
468
|
|
|
421
469
|
if (wanted == 0)
|
|
422
470
|
return py::tuple(5);
|
|
@@ -441,11 +489,11 @@ static py::tuple search_many_in_index( //
|
|
|
441
489
|
|
|
442
490
|
// clang-format off
|
|
443
491
|
switch (numpy_string_to_kind(vectors_info.format)) {
|
|
444
|
-
case scalar_kind_t::b1x8_k: search_typed<b1x8_t>(index, vectors_info, wanted, exact, threads, keys_py, distances_py, counts_py, stats_visited_members, stats_computed_distances); break;
|
|
445
|
-
case scalar_kind_t::i8_k: search_typed<i8_t>(index, vectors_info, wanted, exact, threads, keys_py, distances_py, counts_py, stats_visited_members, stats_computed_distances); break;
|
|
446
|
-
case scalar_kind_t::f16_k: search_typed<f16_t>(index, vectors_info, wanted, exact, threads, keys_py, distances_py, counts_py, stats_visited_members, stats_computed_distances); break;
|
|
447
|
-
case scalar_kind_t::f32_k: search_typed<f32_t>(index, vectors_info, wanted, exact, threads, keys_py, distances_py, counts_py, stats_visited_members, stats_computed_distances); break;
|
|
448
|
-
case scalar_kind_t::f64_k: search_typed<f64_t>(index, vectors_info, wanted, exact, threads, keys_py, distances_py, counts_py, stats_visited_members, stats_computed_distances); break;
|
|
492
|
+
case scalar_kind_t::b1x8_k: search_typed<b1x8_t>(index, vectors_info, wanted, exact, threads, keys_py, distances_py, counts_py, stats_visited_members, stats_computed_distances, progress); break;
|
|
493
|
+
case scalar_kind_t::i8_k: search_typed<i8_t>(index, vectors_info, wanted, exact, threads, keys_py, distances_py, counts_py, stats_visited_members, stats_computed_distances, progress); break;
|
|
494
|
+
case scalar_kind_t::f16_k: search_typed<f16_t>(index, vectors_info, wanted, exact, threads, keys_py, distances_py, counts_py, stats_visited_members, stats_computed_distances, progress); break;
|
|
495
|
+
case scalar_kind_t::f32_k: search_typed<f32_t>(index, vectors_info, wanted, exact, threads, keys_py, distances_py, counts_py, stats_visited_members, stats_computed_distances, progress); break;
|
|
496
|
+
case scalar_kind_t::f64_k: search_typed<f64_t>(index, vectors_info, wanted, exact, threads, keys_py, distances_py, counts_py, stats_visited_members, stats_computed_distances, progress); break;
|
|
449
497
|
default: throw std::invalid_argument("Incompatible scalars in the query matrix: " + vectors_info.format);
|
|
450
498
|
}
|
|
451
499
|
// clang-format on
|
|
@@ -463,7 +511,8 @@ template <typename scalar_at>
|
|
|
463
511
|
static void search_typed_brute_force( //
|
|
464
512
|
py::buffer_info& dataset_info, py::buffer_info& queries_info, //
|
|
465
513
|
std::size_t wanted, std::size_t threads, metric_t const& metric, //
|
|
466
|
-
py::array_t<dense_key_t>& keys_py, py::array_t<distance_t>& distances_py, py::array_t<Py_ssize_t>& counts_py
|
|
514
|
+
py::array_t<dense_key_t>& keys_py, py::array_t<distance_t>& distances_py, py::array_t<Py_ssize_t>& counts_py,
|
|
515
|
+
progress_func_t const& progress) {
|
|
467
516
|
|
|
468
517
|
auto keys_py2d = keys_py.template mutable_unchecked<2>();
|
|
469
518
|
auto distances_py2d = distances_py.template mutable_unchecked<2>();
|
|
@@ -485,6 +534,10 @@ static void search_typed_brute_force( //
|
|
|
485
534
|
if (!query_mutexes)
|
|
486
535
|
throw std::bad_alloc();
|
|
487
536
|
|
|
537
|
+
// Progress status
|
|
538
|
+
progress_t progress_{progress};
|
|
539
|
+
std::atomic<std::size_t> processed{0};
|
|
540
|
+
|
|
488
541
|
executor_default_t{threads}.dynamic(tasks_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
489
542
|
//
|
|
490
543
|
std::size_t dataset_idx = task_idx / queries_count;
|
|
@@ -514,11 +567,15 @@ static void search_typed_brute_force( //
|
|
|
514
567
|
}
|
|
515
568
|
|
|
516
569
|
// We don't want to check for signals from multiple threads
|
|
570
|
+
++processed;
|
|
517
571
|
if (thread_idx == 0)
|
|
518
|
-
if (PyErr_CheckSignals() != 0)
|
|
572
|
+
if (PyErr_CheckSignals() != 0 || !progress_(processed.load(), tasks_count))
|
|
519
573
|
return false;
|
|
520
574
|
return true;
|
|
521
575
|
});
|
|
576
|
+
|
|
577
|
+
// At the end report the latest numbers, because the reporter thread may be finished earlier
|
|
578
|
+
progress_(processed.load(), tasks_count);
|
|
522
579
|
}
|
|
523
580
|
|
|
524
581
|
static py::tuple search_many_brute_force( //
|
|
@@ -526,7 +583,8 @@ static py::tuple search_many_brute_force( //
|
|
|
526
583
|
std::size_t wanted, std::size_t threads, //
|
|
527
584
|
metric_kind_t metric_kind, //
|
|
528
585
|
metric_signature_t metric_signature, //
|
|
529
|
-
std::uintptr_t metric_uintptr
|
|
586
|
+
std::uintptr_t metric_uintptr, //
|
|
587
|
+
progress_func_t const& progress) {
|
|
530
588
|
|
|
531
589
|
if (wanted == 0)
|
|
532
590
|
return py::tuple(5);
|
|
@@ -560,11 +618,11 @@ static py::tuple search_many_brute_force( //
|
|
|
560
618
|
|
|
561
619
|
// clang-format off
|
|
562
620
|
switch (dataset_kind) {
|
|
563
|
-
case scalar_kind_t::b1x8_k: search_typed_brute_force<b1x8_t>(dataset_info, queries_info, wanted, threads, metric, keys_py, distances_py, counts_py); break;
|
|
564
|
-
case scalar_kind_t::i8_k: search_typed_brute_force<i8_t>(dataset_info, queries_info, wanted, threads, metric, keys_py, distances_py, counts_py); break;
|
|
565
|
-
case scalar_kind_t::f16_k: search_typed_brute_force<f16_t>(dataset_info, queries_info, wanted, threads, metric, keys_py, distances_py, counts_py); break;
|
|
566
|
-
case scalar_kind_t::f32_k: search_typed_brute_force<f32_t>(dataset_info, queries_info, wanted, threads, metric, keys_py, distances_py, counts_py); break;
|
|
567
|
-
case scalar_kind_t::f64_k: search_typed_brute_force<f64_t>(dataset_info, queries_info, wanted, threads, metric, keys_py, distances_py, counts_py); break;
|
|
621
|
+
case scalar_kind_t::b1x8_k: search_typed_brute_force<b1x8_t>(dataset_info, queries_info, wanted, threads, metric, keys_py, distances_py, counts_py, progress); break;
|
|
622
|
+
case scalar_kind_t::i8_k: search_typed_brute_force<i8_t>(dataset_info, queries_info, wanted, threads, metric, keys_py, distances_py, counts_py, progress); break;
|
|
623
|
+
case scalar_kind_t::f16_k: search_typed_brute_force<f16_t>(dataset_info, queries_info, wanted, threads, metric, keys_py, distances_py, counts_py, progress); break;
|
|
624
|
+
case scalar_kind_t::f32_k: search_typed_brute_force<f32_t>(dataset_info, queries_info, wanted, threads, metric, keys_py, distances_py, counts_py, progress); break;
|
|
625
|
+
case scalar_kind_t::f64_k: search_typed_brute_force<f64_t>(dataset_info, queries_info, wanted, threads, metric, keys_py, distances_py, counts_py, progress); break;
|
|
568
626
|
default: throw std::invalid_argument("Incompatible vector types: " + dataset_info.format);
|
|
569
627
|
}
|
|
570
628
|
// clang-format on
|
|
@@ -603,7 +661,7 @@ template <typename scalar_at> struct rows_lookup_gt {
|
|
|
603
661
|
template <typename index_at>
|
|
604
662
|
static py::tuple cluster_vectors( //
|
|
605
663
|
index_at& index, py::buffer queries, //
|
|
606
|
-
std::size_t min_count, std::size_t max_count, std::size_t threads) {
|
|
664
|
+
std::size_t min_count, std::size_t max_count, std::size_t threads, progress_func_t const& progress) {
|
|
607
665
|
|
|
608
666
|
if (index.limits().threads_search < threads)
|
|
609
667
|
throw std::invalid_argument("Can't use that many threads!");
|
|
@@ -637,11 +695,11 @@ static py::tuple cluster_vectors( //
|
|
|
637
695
|
|
|
638
696
|
// clang-format off
|
|
639
697
|
switch (numpy_string_to_kind(queries_info.format)) {
|
|
640
|
-
case scalar_kind_t::b1x8_k: cluster_result = index.cluster(queries_begin.as<b1x8_t const>(), queries_end.as<b1x8_t const>(), config, keys_ptr, distances_ptr, executor); break;
|
|
641
|
-
case scalar_kind_t::i8_k: cluster_result = index.cluster(queries_begin.as<i8_t const>(), queries_end.as<i8_t const>(), config, keys_ptr, distances_ptr, executor); break;
|
|
642
|
-
case scalar_kind_t::f16_k: cluster_result = index.cluster(queries_begin.as<f16_t const>(), queries_end.as<f16_t const>(), config, keys_ptr, distances_ptr, executor); break;
|
|
643
|
-
case scalar_kind_t::f32_k: cluster_result = index.cluster(queries_begin.as<f32_t const>(), queries_end.as<f32_t const>(), config, keys_ptr, distances_ptr, executor); break;
|
|
644
|
-
case scalar_kind_t::f64_k: cluster_result = index.cluster(queries_begin.as<f64_t const>(), queries_end.as<f64_t const>(), config, keys_ptr, distances_ptr, executor); break;
|
|
698
|
+
case scalar_kind_t::b1x8_k: cluster_result = index.cluster(queries_begin.as<b1x8_t const>(), queries_end.as<b1x8_t const>(), config, keys_ptr, distances_ptr, executor, progress_t{progress}); break;
|
|
699
|
+
case scalar_kind_t::i8_k: cluster_result = index.cluster(queries_begin.as<i8_t const>(), queries_end.as<i8_t const>(), config, keys_ptr, distances_ptr, executor, progress_t{progress}); break;
|
|
700
|
+
case scalar_kind_t::f16_k: cluster_result = index.cluster(queries_begin.as<f16_t const>(), queries_end.as<f16_t const>(), config, keys_ptr, distances_ptr, executor, progress_t{progress}); break;
|
|
701
|
+
case scalar_kind_t::f32_k: cluster_result = index.cluster(queries_begin.as<f32_t const>(), queries_end.as<f32_t const>(), config, keys_ptr, distances_ptr, executor, progress_t{progress}); break;
|
|
702
|
+
case scalar_kind_t::f64_k: cluster_result = index.cluster(queries_begin.as<f64_t const>(), queries_end.as<f64_t const>(), config, keys_ptr, distances_ptr, executor, progress_t{progress}); break;
|
|
645
703
|
default: throw std::invalid_argument("Incompatible scalars in the query matrix: " + queries_info.format);
|
|
646
704
|
}
|
|
647
705
|
// clang-format on
|
|
@@ -677,7 +735,7 @@ static py::tuple cluster_vectors( //
|
|
|
677
735
|
template <typename index_at>
|
|
678
736
|
static py::tuple cluster_keys( //
|
|
679
737
|
index_at& index, py::array_t<dense_key_t> queries_py, //
|
|
680
|
-
std::size_t min_count, std::size_t max_count, std::size_t threads) {
|
|
738
|
+
std::size_t min_count, std::size_t max_count, std::size_t threads, progress_func_t const& progress) {
|
|
681
739
|
|
|
682
740
|
if (index.limits().threads_search < threads)
|
|
683
741
|
throw std::invalid_argument("Can't use that many threads!");
|
|
@@ -701,7 +759,7 @@ static py::tuple cluster_keys( //
|
|
|
701
759
|
config.max_clusters = max_count;
|
|
702
760
|
|
|
703
761
|
dense_clustering_result_t cluster_result =
|
|
704
|
-
index.cluster(queries_begin, queries_end, config, keys_ptr, distances_ptr, executor);
|
|
762
|
+
index.cluster(queries_begin, queries_end, config, keys_ptr, distances_ptr, executor, progress_t{progress});
|
|
705
763
|
cluster_result.error.raise();
|
|
706
764
|
|
|
707
765
|
// Those would be set to 1 for all entries, in case of success
|
|
@@ -721,7 +779,8 @@ static py::tuple cluster_keys( //
|
|
|
721
779
|
|
|
722
780
|
static std::unordered_map<dense_key_t, dense_key_t> join_index( //
|
|
723
781
|
dense_index_py_t const& a, dense_index_py_t const& b, //
|
|
724
|
-
std::size_t max_proposals, bool exact
|
|
782
|
+
std::size_t max_proposals, bool exact, //
|
|
783
|
+
progress_func_t const& progress) {
|
|
725
784
|
|
|
726
785
|
std::unordered_map<dense_key_t, dense_key_t> a_to_b;
|
|
727
786
|
dummy_key_to_key_mapping_t b_to_a;
|
|
@@ -733,7 +792,7 @@ static std::unordered_map<dense_key_t, dense_key_t> join_index( //
|
|
|
733
792
|
config.expansion = (std::max)(a.expansion_search(), b.expansion_search());
|
|
734
793
|
std::size_t threads = (std::min)(a.limits().threads(), b.limits().threads());
|
|
735
794
|
executor_default_t executor{threads};
|
|
736
|
-
join_result_t result = a.join(b, config, a_to_b, b_to_a, executor);
|
|
795
|
+
join_result_t result = a.join(b, config, a_to_b, b_to_a, executor, progress_t{progress});
|
|
737
796
|
forward_error(result);
|
|
738
797
|
|
|
739
798
|
return a_to_b;
|
|
@@ -749,14 +808,14 @@ static dense_index_py_t copy_index(dense_index_py_t const& index, bool force_cop
|
|
|
749
808
|
return std::move(result.index);
|
|
750
809
|
}
|
|
751
810
|
|
|
752
|
-
static void compact_index(dense_index_py_t& index, std::size_t threads) {
|
|
811
|
+
static void compact_index(dense_index_py_t& index, std::size_t threads, progress_func_t const& progress) {
|
|
753
812
|
|
|
754
813
|
if (!threads)
|
|
755
814
|
threads = std::thread::hardware_concurrency();
|
|
756
815
|
if (!index.reserve(index_limits_t(index.size(), threads)))
|
|
757
816
|
throw std::invalid_argument("Out of memory!");
|
|
758
817
|
|
|
759
|
-
index.compact(executor_default_t{threads});
|
|
818
|
+
index.compact(executor_default_t{threads}, progress_t{progress});
|
|
760
819
|
}
|
|
761
820
|
|
|
762
821
|
static py::dict index_metadata(index_dense_metadata_result_t const& meta) {
|
|
@@ -782,9 +841,9 @@ static py::dict index_metadata(index_dense_metadata_result_t const& meta) {
|
|
|
782
841
|
}
|
|
783
842
|
|
|
784
843
|
// clang-format off
|
|
785
|
-
template <typename index_at> void save_index_to_path(index_at const& index, std::string const& path) { index.save(path.c_str()).error.raise(); }
|
|
786
|
-
template <typename index_at> void load_index_from_path(index_at& index, std::string const& path) { index.load(path.c_str()).error.raise(); }
|
|
787
|
-
template <typename index_at> void view_index_from_path(index_at& index, std::string const& path) { index.view(path.c_str()).error.raise(); }
|
|
844
|
+
template <typename index_at> void save_index_to_path(index_at const& index, std::string const& path, progress_func_t const& progress) { index.save(path.c_str(), {}, progress_t{progress}).error.raise(); }
|
|
845
|
+
template <typename index_at> void load_index_from_path(index_at& index, std::string const& path, progress_func_t const& progress) { index.load(path.c_str(), {}, progress_t{progress}).error.raise(); }
|
|
846
|
+
template <typename index_at> void view_index_from_path(index_at& index, std::string const& path, progress_func_t const& progress) { index.view(path.c_str(), 0, {}, progress_t{progress}).error.raise(); }
|
|
788
847
|
template <typename index_at> void reset_index(index_at& index) { index.reset(); }
|
|
789
848
|
template <typename index_at> void clear_index(index_at& index) { index.clear(); }
|
|
790
849
|
template <typename index_at> std::size_t max_level(index_at const &index) { return index.max_level(); }
|
|
@@ -798,7 +857,7 @@ template <typename py_bytes_at> memory_mapped_file_t memory_map_from_bytes(py_by
|
|
|
798
857
|
return {(byte_t*)(info.ptr), static_cast<std::size_t>(info.size)};
|
|
799
858
|
}
|
|
800
859
|
|
|
801
|
-
template <typename index_at> py::object save_index_to_buffer(index_at const& index) {
|
|
860
|
+
template <typename index_at> py::object save_index_to_buffer(index_at const& index, progress_func_t const& progress) {
|
|
802
861
|
std::size_t serialized_length = index.serialized_length();
|
|
803
862
|
|
|
804
863
|
// Create an empty bytearray object using CPython API
|
|
@@ -814,7 +873,7 @@ template <typename index_at> py::object save_index_to_buffer(index_at const& ind
|
|
|
814
873
|
|
|
815
874
|
char* buffer = PyByteArray_AS_STRING(byte_array);
|
|
816
875
|
memory_mapped_file_t memory_map((byte_t*)buffer, serialized_length);
|
|
817
|
-
serialization_result_t result = index.save(std::move(memory_map));
|
|
876
|
+
serialization_result_t result = index.save(std::move(memory_map), {}, {}, progress_t{progress});
|
|
818
877
|
|
|
819
878
|
if (!result) {
|
|
820
879
|
Py_XDECREF(byte_array);
|
|
@@ -824,11 +883,13 @@ template <typename index_at> py::object save_index_to_buffer(index_at const& ind
|
|
|
824
883
|
return py::reinterpret_steal<py::object>(byte_array);
|
|
825
884
|
}
|
|
826
885
|
|
|
827
|
-
template <typename index_at>
|
|
828
|
-
|
|
886
|
+
template <typename index_at>
|
|
887
|
+
void load_index_from_buffer(index_at& index, py::bytes const& buffer, progress_func_t const& progress) {
|
|
888
|
+
index.load(memory_map_from_bytes(buffer), {}, {}, progress_t{progress}).error.raise();
|
|
829
889
|
}
|
|
830
|
-
template <typename index_at>
|
|
831
|
-
|
|
890
|
+
template <typename index_at>
|
|
891
|
+
void view_index_from_buffer(index_at& index, py::bytes const& buffer, progress_func_t const& progress) {
|
|
892
|
+
index.view(memory_map_from_bytes(buffer), {}, {}, progress_t{progress}).error.raise();
|
|
832
893
|
}
|
|
833
894
|
|
|
834
895
|
template <typename index_at> std::vector<typename index_at::stats_t> compute_levels_stats(index_at const& index) {
|
|
@@ -954,7 +1015,8 @@ PYBIND11_MODULE(compiled, m) {
|
|
|
954
1015
|
py::arg("threads") = 0, //
|
|
955
1016
|
py::arg("metric_kind") = metric_kind_t::cos_k, //
|
|
956
1017
|
py::arg("metric_signature") = metric_signature_t::array_array_k, //
|
|
957
|
-
py::arg("metric_pointer") = 0
|
|
1018
|
+
py::arg("metric_pointer") = 0, //
|
|
1019
|
+
py::arg("progress") = nullptr //
|
|
958
1020
|
);
|
|
959
1021
|
|
|
960
1022
|
m.def(
|
|
@@ -989,7 +1051,8 @@ PYBIND11_MODULE(compiled, m) {
|
|
|
989
1051
|
py::arg("vectors"), //
|
|
990
1052
|
py::kw_only(), //
|
|
991
1053
|
py::arg("copy") = true, //
|
|
992
|
-
py::arg("threads") = 0
|
|
1054
|
+
py::arg("threads") = 0, //
|
|
1055
|
+
py::arg("progress") = nullptr //
|
|
993
1056
|
);
|
|
994
1057
|
|
|
995
1058
|
i.def( //
|
|
@@ -997,7 +1060,8 @@ PYBIND11_MODULE(compiled, m) {
|
|
|
997
1060
|
py::arg("queries"), //
|
|
998
1061
|
py::arg("count") = 10, //
|
|
999
1062
|
py::arg("exact") = false, //
|
|
1000
|
-
py::arg("threads") = 0
|
|
1063
|
+
py::arg("threads") = 0, //
|
|
1064
|
+
py::arg("progress") = nullptr //
|
|
1001
1065
|
);
|
|
1002
1066
|
|
|
1003
1067
|
i.def( //
|
|
@@ -1005,7 +1069,8 @@ PYBIND11_MODULE(compiled, m) {
|
|
|
1005
1069
|
py::arg("queries"), //
|
|
1006
1070
|
py::arg("min_count") = 0, //
|
|
1007
1071
|
py::arg("max_count") = 0, //
|
|
1008
|
-
py::arg("threads") = 0
|
|
1072
|
+
py::arg("threads") = 0, //
|
|
1073
|
+
py::arg("progress") = nullptr //
|
|
1009
1074
|
);
|
|
1010
1075
|
|
|
1011
1076
|
i.def( //
|
|
@@ -1013,7 +1078,8 @@ PYBIND11_MODULE(compiled, m) {
|
|
|
1013
1078
|
py::arg("queries"), //
|
|
1014
1079
|
py::arg("min_count") = 0, //
|
|
1015
1080
|
py::arg("max_count") = 0, //
|
|
1016
|
-
py::arg("threads") = 0
|
|
1081
|
+
py::arg("threads") = 0, //
|
|
1082
|
+
py::arg("progress") = nullptr //
|
|
1017
1083
|
);
|
|
1018
1084
|
|
|
1019
1085
|
i.def(
|
|
@@ -1205,19 +1271,24 @@ PYBIND11_MODULE(compiled, m) {
|
|
|
1205
1271
|
},
|
|
1206
1272
|
py::arg("offset"));
|
|
1207
1273
|
|
|
1208
|
-
i.def("save_index_to_path", &save_index_to_path<dense_index_py_t
|
|
1209
|
-
i.def("load_index_from_path", &load_index_from_path<dense_index_py_t
|
|
1210
|
-
|
|
1274
|
+
i.def("save_index_to_path", &save_index_to_path<dense_index_py_t>, py::arg("path"), py::arg("progress") = nullptr);
|
|
1275
|
+
i.def("load_index_from_path", &load_index_from_path<dense_index_py_t>, py::arg("path"),
|
|
1276
|
+
py::arg("progress") = nullptr);
|
|
1277
|
+
i.def("view_index_from_path", &view_index_from_path<dense_index_py_t>, py::arg("path"),
|
|
1278
|
+
py::arg("progress") = nullptr);
|
|
1211
1279
|
|
|
1212
|
-
i.def("save_index_to_buffer", &save_index_to_buffer<dense_index_py_t
|
|
1213
|
-
i.def("load_index_from_buffer", &load_index_from_buffer<dense_index_py_t
|
|
1214
|
-
|
|
1280
|
+
i.def("save_index_to_buffer", &save_index_to_buffer<dense_index_py_t>, py::arg("progress") = nullptr);
|
|
1281
|
+
i.def("load_index_from_buffer", &load_index_from_buffer<dense_index_py_t>, py::arg("buffer"),
|
|
1282
|
+
py::arg("progress") = nullptr);
|
|
1283
|
+
i.def("view_index_from_buffer", &view_index_from_buffer<dense_index_py_t>, py::arg("buffer"),
|
|
1284
|
+
py::arg("progress") = nullptr);
|
|
1215
1285
|
|
|
1216
1286
|
i.def("reset", &reset_index<dense_index_py_t>);
|
|
1217
1287
|
i.def("clear", &clear_index<dense_index_py_t>);
|
|
1218
1288
|
i.def("copy", ©_index, py::kw_only(), py::arg("copy") = true);
|
|
1219
|
-
i.def("compact", &compact_index);
|
|
1220
|
-
i.def("join", &join_index, py::arg("other"), py::arg("max_proposals") = 0, py::arg("exact") = false
|
|
1289
|
+
i.def("compact", &compact_index, py::arg("threads"), py::arg("progress") = nullptr);
|
|
1290
|
+
i.def("join", &join_index, py::arg("other"), py::arg("max_proposals") = 0, py::arg("exact") = false,
|
|
1291
|
+
py::arg("progress") = nullptr);
|
|
1221
1292
|
|
|
1222
1293
|
using punned_index_stats_t = typename dense_index_py_t::stats_t;
|
|
1223
1294
|
auto i_stats = py::class_<punned_index_stats_t>(m, "IndexStats");
|
|
@@ -1242,6 +1313,7 @@ PYBIND11_MODULE(compiled, m) {
|
|
|
1242
1313
|
py::arg("query"), //
|
|
1243
1314
|
py::arg("count") = 10, //
|
|
1244
1315
|
py::arg("exact") = false, //
|
|
1245
|
-
py::arg("threads") = 0
|
|
1316
|
+
py::arg("threads") = 0, //
|
|
1317
|
+
py::arg("progress") = nullptr //
|
|
1246
1318
|
);
|
|
1247
1319
|
}
|
package/python/usearch/index.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
+
from inspect import signature
|
|
2
3
|
|
|
3
4
|
# The purpose of this file is to provide Pythonic wrapper on top
|
|
4
5
|
# the native precompiled CPython module. It improves compatibility
|
|
@@ -8,6 +9,7 @@ import os
|
|
|
8
9
|
import math
|
|
9
10
|
from dataclasses import dataclass
|
|
10
11
|
from typing import (
|
|
12
|
+
Any,
|
|
11
13
|
Optional,
|
|
12
14
|
Union,
|
|
13
15
|
NamedTuple,
|
|
@@ -72,6 +74,17 @@ NoneType = type(None)
|
|
|
72
74
|
|
|
73
75
|
PathOrBuffer = Union[str, os.PathLike, bytes]
|
|
74
76
|
|
|
77
|
+
ProgressCallback = Callable[[int, int], bool]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _match_signature(
|
|
81
|
+
func: Callable[[Any], Any], arg_types: List[type], ret_type: type
|
|
82
|
+
) -> bool:
|
|
83
|
+
assert callable(func), "Not callable"
|
|
84
|
+
sig = signature(func)
|
|
85
|
+
param_types = [param.annotation for param in sig.parameters.values()]
|
|
86
|
+
return param_types == arg_types and sig.return_annotation == ret_type
|
|
87
|
+
|
|
75
88
|
|
|
76
89
|
def _normalize_dtype(
|
|
77
90
|
dtype,
|
|
@@ -153,12 +166,16 @@ def _search_in_compiled(
|
|
|
153
166
|
vectors: np.ndarray,
|
|
154
167
|
*,
|
|
155
168
|
log: Union[str, bool],
|
|
156
|
-
|
|
169
|
+
progress: Optional[ProgressCallback],
|
|
157
170
|
**kwargs,
|
|
158
171
|
) -> Union[Matches, BatchMatches]:
|
|
159
172
|
#
|
|
160
173
|
assert isinstance(vectors, np.ndarray), "Expects a NumPy array"
|
|
161
174
|
assert vectors.ndim == 1 or vectors.ndim == 2, "Expects a matrix or vector"
|
|
175
|
+
assert not progress or _match_signature(
|
|
176
|
+
progress, [int, int], bool
|
|
177
|
+
), "Invalid callback"
|
|
178
|
+
|
|
162
179
|
if vectors.ndim == 1:
|
|
163
180
|
vectors = vectors.reshape(1, len(vectors))
|
|
164
181
|
count_vectors = vectors.shape[0]
|
|
@@ -168,42 +185,25 @@ def _search_in_compiled(
|
|
|
168
185
|
) -> Union[BatchMatches, Matches]:
|
|
169
186
|
return batch_matches[0] if count_vectors == 1 else batch_matches
|
|
170
187
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
if batch_size:
|
|
175
|
-
tasks = [
|
|
176
|
-
vectors[start_row : start_row + batch_size, :]
|
|
177
|
-
for start_row in range(0, count_vectors, batch_size)
|
|
178
|
-
]
|
|
179
|
-
tasks_matches = []
|
|
188
|
+
# Create progress bar if needed
|
|
189
|
+
if log:
|
|
180
190
|
name = log if isinstance(log, str) else "Search"
|
|
181
191
|
pbar = tqdm(
|
|
182
|
-
tasks,
|
|
183
192
|
desc=name,
|
|
184
193
|
total=count_vectors,
|
|
185
194
|
unit="vector",
|
|
186
|
-
disable=log is False,
|
|
187
195
|
)
|
|
188
|
-
for vectors in tasks:
|
|
189
|
-
tuple_ = compiled_callable(vectors, **kwargs)
|
|
190
|
-
tasks_matches.append(BatchMatches(*tuple_))
|
|
191
|
-
pbar.update(vectors.shape[0])
|
|
192
196
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
keys=np.vstack([m.keys for m in tasks_matches]),
|
|
197
|
-
distances=np.vstack([m.distances for m in tasks_matches]),
|
|
198
|
-
counts=np.concatenate([m.counts for m in tasks_matches], axis=None),
|
|
199
|
-
visited_members=sum([m.visited_members for m in tasks_matches]),
|
|
200
|
-
computed_distances=sum([m.computed_distances for m in tasks_matches]),
|
|
201
|
-
)
|
|
202
|
-
)
|
|
197
|
+
def update_progress_bar(processed: int, total: int) -> bool:
|
|
198
|
+
pbar.update(processed - pbar.n)
|
|
199
|
+
return progress if progress else True
|
|
203
200
|
|
|
201
|
+
tuple_ = compiled_callable(vectors, progress=update_progress_bar, **kwargs)
|
|
202
|
+
pbar.close()
|
|
204
203
|
else:
|
|
205
204
|
tuple_ = compiled_callable(vectors, **kwargs)
|
|
206
|
-
|
|
205
|
+
|
|
206
|
+
return distil_batch(BatchMatches(*tuple_))
|
|
207
207
|
|
|
208
208
|
|
|
209
209
|
def _add_to_compiled(
|
|
@@ -214,9 +214,13 @@ def _add_to_compiled(
|
|
|
214
214
|
copy: bool,
|
|
215
215
|
threads: int,
|
|
216
216
|
log: Union[str, bool],
|
|
217
|
-
|
|
217
|
+
progress: Optional[ProgressCallback],
|
|
218
218
|
) -> Union[int, np.ndarray]:
|
|
219
|
+
#
|
|
219
220
|
assert isinstance(vectors, np.ndarray), "Expects a NumPy array"
|
|
221
|
+
assert not progress or _match_signature(
|
|
222
|
+
progress, [int, int], bool
|
|
223
|
+
), "Invalid callback"
|
|
220
224
|
assert vectors.ndim == 1 or vectors.ndim == 2, "Expects a matrix or vector"
|
|
221
225
|
if vectors.ndim == 1:
|
|
222
226
|
vectors = vectors.reshape(1, len(vectors))
|
|
@@ -235,37 +239,29 @@ def _add_to_compiled(
|
|
|
235
239
|
|
|
236
240
|
assert len(keys) == count_vectors
|
|
237
241
|
|
|
238
|
-
#
|
|
239
|
-
if log
|
|
240
|
-
batch_size = int(math.ceil(count_vectors / 100))
|
|
241
|
-
|
|
242
|
-
# Split into batches and log progress, if needed
|
|
243
|
-
if batch_size:
|
|
244
|
-
keys = [
|
|
245
|
-
keys[start_row : start_row + batch_size]
|
|
246
|
-
for start_row in range(0, count_vectors, batch_size)
|
|
247
|
-
]
|
|
248
|
-
vectors = [
|
|
249
|
-
vectors[start_row : start_row + batch_size, :]
|
|
250
|
-
for start_row in range(0, count_vectors, batch_size)
|
|
251
|
-
]
|
|
252
|
-
tasks = zip(keys, vectors)
|
|
242
|
+
# Create progress bar if needed
|
|
243
|
+
if log:
|
|
253
244
|
name = log if isinstance(log, str) else "Add"
|
|
254
245
|
pbar = tqdm(
|
|
255
|
-
tasks,
|
|
256
246
|
desc=name,
|
|
257
247
|
total=count_vectors,
|
|
258
248
|
unit="vector",
|
|
259
|
-
disable=log is False,
|
|
260
249
|
)
|
|
261
|
-
for keys, vectors in tasks:
|
|
262
|
-
compiled.add_many(keys, vectors, copy=copy, threads=threads)
|
|
263
|
-
pbar.update(len(keys))
|
|
264
250
|
|
|
265
|
-
|
|
251
|
+
def update_progress_bar(processed: int, total: int) -> bool:
|
|
252
|
+
pbar.update(processed - pbar.n)
|
|
253
|
+
return progress(processed, total) if progress else True
|
|
266
254
|
|
|
255
|
+
compiled.add_many(
|
|
256
|
+
keys,
|
|
257
|
+
vectors,
|
|
258
|
+
copy=copy,
|
|
259
|
+
threads=threads,
|
|
260
|
+
progress=update_progress_bar,
|
|
261
|
+
)
|
|
262
|
+
pbar.close()
|
|
267
263
|
else:
|
|
268
|
-
compiled.add_many(keys, vectors, copy=copy, threads=threads)
|
|
264
|
+
compiled.add_many(keys, vectors, copy=copy, threads=threads, progress=progress)
|
|
269
265
|
|
|
270
266
|
return keys
|
|
271
267
|
|
|
@@ -625,7 +621,7 @@ class Index:
|
|
|
625
621
|
copy: bool = True,
|
|
626
622
|
threads: int = 0,
|
|
627
623
|
log: Union[str, bool] = False,
|
|
628
|
-
|
|
624
|
+
progress: Optional[ProgressCallback] = None,
|
|
629
625
|
) -> Union[int, np.ndarray]:
|
|
630
626
|
"""Inserts one or move vectors into the index.
|
|
631
627
|
|
|
@@ -651,8 +647,8 @@ class Index:
|
|
|
651
647
|
:type threads: int, defaults to 0
|
|
652
648
|
:param log: Whether to print the progress bar
|
|
653
649
|
:type log: Union[str, bool], defaults to False
|
|
654
|
-
:param
|
|
655
|
-
:type
|
|
650
|
+
:param progress: Callback to report stats of the progress and control it
|
|
651
|
+
:type progress: Optional[ProgressCallback], defaults to None
|
|
656
652
|
:return: Inserted key or keys
|
|
657
653
|
:type: Union[int, np.ndarray]
|
|
658
654
|
"""
|
|
@@ -663,7 +659,7 @@ class Index:
|
|
|
663
659
|
copy=copy,
|
|
664
660
|
threads=threads,
|
|
665
661
|
log=log,
|
|
666
|
-
|
|
662
|
+
progress=progress,
|
|
667
663
|
)
|
|
668
664
|
|
|
669
665
|
def search(
|
|
@@ -675,7 +671,7 @@ class Index:
|
|
|
675
671
|
threads: int = 0,
|
|
676
672
|
exact: bool = False,
|
|
677
673
|
log: Union[str, bool] = False,
|
|
678
|
-
|
|
674
|
+
progress: Optional[ProgressCallback] = None,
|
|
679
675
|
) -> Union[Matches, BatchMatches]:
|
|
680
676
|
"""
|
|
681
677
|
Performs approximate nearest neighbors search for one or more queries.
|
|
@@ -690,8 +686,8 @@ class Index:
|
|
|
690
686
|
:type exact: bool, defaults to False
|
|
691
687
|
:param log: Whether to print the progress bar, default to False
|
|
692
688
|
:type log: Union[str, bool], optional
|
|
693
|
-
:param
|
|
694
|
-
:type
|
|
689
|
+
:param progress: Callback to report stats of the progress and control it
|
|
690
|
+
:type progress: Optional[ProgressCallback], defaults to None
|
|
695
691
|
:return: Matches for one or more queries
|
|
696
692
|
:rtype: Union[Matches, BatchMatches]
|
|
697
693
|
"""
|
|
@@ -701,11 +697,11 @@ class Index:
|
|
|
701
697
|
vectors,
|
|
702
698
|
# Batch scheduling:
|
|
703
699
|
log=log,
|
|
704
|
-
batch_size=batch_size,
|
|
705
700
|
# Search constraints:
|
|
706
701
|
count=count,
|
|
707
702
|
exact=exact,
|
|
708
703
|
threads=threads,
|
|
704
|
+
progress=progress,
|
|
709
705
|
)
|
|
710
706
|
|
|
711
707
|
def contains(self, keys: KeyOrKeysLike) -> Union[bool, np.ndarray]:
|
|
@@ -932,37 +928,57 @@ class Index:
|
|
|
932
928
|
self._compiled.expansion_search = v
|
|
933
929
|
|
|
934
930
|
def save(
|
|
935
|
-
self,
|
|
931
|
+
self,
|
|
932
|
+
path_or_buffer: Union[str, os.PathLike, NoneType] = None,
|
|
933
|
+
progress: Optional[ProgressCallback] = None,
|
|
936
934
|
) -> Optional[bytes]:
|
|
935
|
+
assert not progress or _match_signature(
|
|
936
|
+
progress, [int, int], bool
|
|
937
|
+
), "Invalid callback signature"
|
|
938
|
+
|
|
937
939
|
path_or_buffer = path_or_buffer if path_or_buffer else self.path
|
|
938
940
|
if path_or_buffer is None:
|
|
939
|
-
return self._compiled.save_index_to_buffer()
|
|
941
|
+
return self._compiled.save_index_to_buffer(progress)
|
|
940
942
|
else:
|
|
941
|
-
self._compiled.save_index_to_path(os.fspath(path_or_buffer))
|
|
943
|
+
self._compiled.save_index_to_path(os.fspath(path_or_buffer), progress)
|
|
944
|
+
|
|
945
|
+
def load(
|
|
946
|
+
self,
|
|
947
|
+
path_or_buffer: Union[str, os.PathLike, bytes, NoneType] = None,
|
|
948
|
+
progress: Optional[ProgressCallback] = None,
|
|
949
|
+
):
|
|
950
|
+
assert not progress or _match_signature(
|
|
951
|
+
progress, [int, int], bool
|
|
952
|
+
), "Invalid callback signature"
|
|
942
953
|
|
|
943
|
-
def load(self, path_or_buffer: Union[str, os.PathLike, bytes, NoneType] = None):
|
|
944
954
|
path_or_buffer = path_or_buffer if path_or_buffer else self.path
|
|
945
955
|
if path_or_buffer is None:
|
|
946
956
|
raise Exception("Define the source")
|
|
947
957
|
if isinstance(path_or_buffer, bytearray):
|
|
948
958
|
path_or_buffer = bytes(path_or_buffer)
|
|
949
959
|
if isinstance(path_or_buffer, bytes):
|
|
950
|
-
self._compiled.load_index_from_buffer(path_or_buffer)
|
|
960
|
+
self._compiled.load_index_from_buffer(path_or_buffer, progress)
|
|
951
961
|
else:
|
|
952
|
-
self._compiled.load_index_from_path(os.fspath(path_or_buffer))
|
|
962
|
+
self._compiled.load_index_from_path(os.fspath(path_or_buffer), progress)
|
|
953
963
|
|
|
954
964
|
def view(
|
|
955
|
-
self,
|
|
965
|
+
self,
|
|
966
|
+
path_or_buffer: Union[str, os.PathLike, bytes, bytearray, NoneType] = None,
|
|
967
|
+
progress: Optional[ProgressCallback] = None,
|
|
956
968
|
):
|
|
969
|
+
assert not progress or _match_signature(
|
|
970
|
+
progress, [int, int], bool
|
|
971
|
+
), "Invalid callback signature"
|
|
972
|
+
|
|
957
973
|
path_or_buffer = path_or_buffer if path_or_buffer else self.path
|
|
958
974
|
if path_or_buffer is None:
|
|
959
975
|
raise Exception("Define the source")
|
|
960
976
|
if isinstance(path_or_buffer, bytearray):
|
|
961
977
|
path_or_buffer = bytes(path_or_buffer)
|
|
962
978
|
if isinstance(path_or_buffer, bytes):
|
|
963
|
-
self._compiled.view_index_from_buffer(path_or_buffer)
|
|
979
|
+
self._compiled.view_index_from_buffer(path_or_buffer, progress)
|
|
964
980
|
else:
|
|
965
|
-
self._compiled.view_index_from_path(os.fspath(path_or_buffer))
|
|
981
|
+
self._compiled.view_index_from_path(os.fspath(path_or_buffer), progress)
|
|
966
982
|
|
|
967
983
|
def clear(self):
|
|
968
984
|
"""Erases all the vectors from the index, preserving the space for future insertions."""
|
|
@@ -993,6 +1009,7 @@ class Index:
|
|
|
993
1009
|
other: Index,
|
|
994
1010
|
max_proposals: int = 0,
|
|
995
1011
|
exact: bool = False,
|
|
1012
|
+
progress: Optional[ProgressCallback] = None,
|
|
996
1013
|
) -> Dict[Key, Key]:
|
|
997
1014
|
"""Performs "Semantic Join" or pairwise matching between `self` & `other` index.
|
|
998
1015
|
Is different from `search`, as no collisions are allowed in resulting pairs.
|
|
@@ -1005,13 +1022,20 @@ class Index:
|
|
|
1005
1022
|
:type max_proposals: int, optional
|
|
1006
1023
|
:param exact: Controls if underlying `search` should be exact, defaults to False
|
|
1007
1024
|
:type exact: bool, optional
|
|
1025
|
+
:param progress: Callback to report stats of the progress and control it
|
|
1026
|
+
:type progress: Optional[ProgressCallback], defaults to None
|
|
1008
1027
|
:return: Mapping from keys of `self` to keys of `other`
|
|
1009
1028
|
:rtype: Dict[Key, Key]
|
|
1010
1029
|
"""
|
|
1030
|
+
assert not progress or _match_signature(
|
|
1031
|
+
progress, [int, int], bool
|
|
1032
|
+
), "Invalid callback signature"
|
|
1033
|
+
|
|
1011
1034
|
return self._compiled.join(
|
|
1012
1035
|
other=other._compiled,
|
|
1013
1036
|
max_proposals=max_proposals,
|
|
1014
1037
|
exact=exact,
|
|
1038
|
+
progress=progress,
|
|
1015
1039
|
)
|
|
1016
1040
|
|
|
1017
1041
|
def cluster(
|
|
@@ -1023,7 +1047,7 @@ class Index:
|
|
|
1023
1047
|
max_count: Optional[int] = None,
|
|
1024
1048
|
threads: int = 0,
|
|
1025
1049
|
log: Union[str, bool] = False,
|
|
1026
|
-
|
|
1050
|
+
progress: Optional[ProgressCallback] = None,
|
|
1027
1051
|
) -> Clustering:
|
|
1028
1052
|
"""
|
|
1029
1053
|
Clusters already indexed or provided `vectors`, mapping them to various centroids.
|
|
@@ -1037,11 +1061,15 @@ class Index:
|
|
|
1037
1061
|
:type threads: int, defaults to 0
|
|
1038
1062
|
:param log: Whether to print the progress bar
|
|
1039
1063
|
:type log: Union[str, bool], defaults to False
|
|
1040
|
-
:param
|
|
1041
|
-
:type
|
|
1064
|
+
:param progress: Callback to report stats of the progress and control it
|
|
1065
|
+
:type progress: Optional[ProgressCallback], defaults to None
|
|
1042
1066
|
:return: Matches for one or more queries
|
|
1043
1067
|
:rtype: Union[Matches, BatchMatches]
|
|
1044
1068
|
"""
|
|
1069
|
+
assert not progress or _match_signature(
|
|
1070
|
+
progress, [int, int], bool
|
|
1071
|
+
), "Invalid callback signature"
|
|
1072
|
+
|
|
1045
1073
|
if min_count is None:
|
|
1046
1074
|
min_count = 0
|
|
1047
1075
|
if max_count is None:
|
|
@@ -1054,6 +1082,7 @@ class Index:
|
|
|
1054
1082
|
min_count=min_count,
|
|
1055
1083
|
max_count=max_count,
|
|
1056
1084
|
threads=threads,
|
|
1085
|
+
progress=progress,
|
|
1057
1086
|
)
|
|
1058
1087
|
else:
|
|
1059
1088
|
if keys is None:
|
|
@@ -1066,6 +1095,7 @@ class Index:
|
|
|
1066
1095
|
min_count=min_count,
|
|
1067
1096
|
max_count=max_count,
|
|
1068
1097
|
threads=threads,
|
|
1098
|
+
progress=progress,
|
|
1069
1099
|
)
|
|
1070
1100
|
|
|
1071
1101
|
batch_matches = BatchMatches(*results)
|
|
@@ -1231,17 +1261,18 @@ class Indexes:
|
|
|
1231
1261
|
*,
|
|
1232
1262
|
threads: int = 0,
|
|
1233
1263
|
exact: bool = False,
|
|
1264
|
+
progress: Optional[ProgressCallback] = None,
|
|
1234
1265
|
):
|
|
1235
1266
|
return _search_in_compiled(
|
|
1236
1267
|
self._compiled.search_many,
|
|
1237
1268
|
vectors,
|
|
1238
1269
|
# Batch scheduling:
|
|
1239
1270
|
log=False,
|
|
1240
|
-
batch_size=None,
|
|
1241
1271
|
# Search constraints:
|
|
1242
1272
|
count=count,
|
|
1243
1273
|
exact=exact,
|
|
1244
1274
|
threads=threads,
|
|
1275
|
+
progress=progress,
|
|
1245
1276
|
)
|
|
1246
1277
|
|
|
1247
1278
|
|
|
@@ -1254,7 +1285,7 @@ def search(
|
|
|
1254
1285
|
exact: bool = False,
|
|
1255
1286
|
threads: int = 0,
|
|
1256
1287
|
log: Union[str, bool] = False,
|
|
1257
|
-
|
|
1288
|
+
progress: Optional[ProgressCallback] = None,
|
|
1258
1289
|
) -> Union[Matches, BatchMatches]:
|
|
1259
1290
|
"""Shortcut for search, that can avoid index construction. Particularly useful for
|
|
1260
1291
|
tiny datasets, where brute-force exact search works fast enough.
|
|
@@ -1279,12 +1310,14 @@ def search(
|
|
|
1279
1310
|
:type exact: bool, optional
|
|
1280
1311
|
:param log: Whether to print the progress bar, default to False
|
|
1281
1312
|
:type log: Union[str, bool], optional
|
|
1282
|
-
:param
|
|
1283
|
-
:type
|
|
1284
|
-
|
|
1313
|
+
:param progress: Callback to report stats of the progress and control it
|
|
1314
|
+
:type progress: Optional[ProgressCallback], defaults to None
|
|
1285
1315
|
:return: Matches for one or more queries
|
|
1286
1316
|
:rtype: Union[Matches, BatchMatches]
|
|
1287
1317
|
"""
|
|
1318
|
+
assert not progress or _match_signature(
|
|
1319
|
+
progress, [int, int], bool
|
|
1320
|
+
), "Invalid callback signature"
|
|
1288
1321
|
assert dataset.ndim == 2, "Dataset must be a matrix, with a vector in each row"
|
|
1289
1322
|
|
|
1290
1323
|
if not exact:
|
|
@@ -1298,14 +1331,14 @@ def search(
|
|
|
1298
1331
|
dataset,
|
|
1299
1332
|
threads=threads,
|
|
1300
1333
|
log=log,
|
|
1301
|
-
|
|
1334
|
+
progress=progress,
|
|
1302
1335
|
)
|
|
1303
1336
|
return index.search(
|
|
1304
1337
|
query,
|
|
1305
1338
|
count,
|
|
1306
1339
|
threads=threads,
|
|
1307
1340
|
log=log,
|
|
1308
|
-
|
|
1341
|
+
progress=progress,
|
|
1309
1342
|
)
|
|
1310
1343
|
|
|
1311
1344
|
metric = _normalize_metric(metric)
|
|
@@ -1329,8 +1362,8 @@ def search(
|
|
|
1329
1362
|
dataset,
|
|
1330
1363
|
query,
|
|
1331
1364
|
metric_kind=metric_kind,
|
|
1332
|
-
metric_pointer=metric_pointer,
|
|
1333
1365
|
metric_signature=metric_signature,
|
|
1366
|
+
metric_pointer=metric_pointer,
|
|
1334
1367
|
**kwargs,
|
|
1335
1368
|
)
|
|
1336
1369
|
|
|
@@ -1339,8 +1372,8 @@ def search(
|
|
|
1339
1372
|
query,
|
|
1340
1373
|
# Batch scheduling:
|
|
1341
1374
|
log=log,
|
|
1342
|
-
batch_size=batch_size,
|
|
1343
1375
|
# Search constraints:
|
|
1344
1376
|
count=count,
|
|
1345
1377
|
threads=threads,
|
|
1378
|
+
progress=progress,
|
|
1346
1379
|
)
|