usearch 0.22.3 → 1.1.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/.github/workflows/prerelease.yml +3 -3
- package/.releaserc +10 -2
- package/.vscode/launch.json +10 -10
- package/.vscode/settings.json +9 -1
- package/.vscode/tasks.json +4 -4
- package/CMakeLists.txt +6 -1
- package/Cargo.toml +2 -2
- package/README.md +35 -41
- package/build.rs +2 -2
- package/c/CMakeLists.txt +48 -0
- package/c/README.md +34 -0
- package/c/lib.cpp +83 -72
- package/c/test.c +20 -20
- package/c/usearch.h +132 -32
- package/conanfile.py +1 -1
- package/cpp/CMakeLists.txt +3 -1
- package/cpp/README.md +16 -16
- package/cpp/bench.cpp +50 -126
- package/cpp/test.cpp +164 -80
- package/docs/benchmarks.md +26 -5
- package/docs/c/reference.rst +3 -3
- package/docs/compilation.md +31 -49
- package/docs/cpp/index.rst +1 -1
- package/docs/cpp/reference.rst +4 -4
- package/golang/lib.go +275 -141
- package/golang/lib_test.go +105 -0
- package/include/usearch/index.hpp +1681 -1656
- package/include/usearch/index_dense.hpp +1400 -0
- package/include/usearch/index_plugins.hpp +1263 -0
- package/java/README.md +1 -1
- package/java/cloud/unum/usearch/Index.java +10 -10
- package/java/cloud/unum/usearch/cloud_unum_usearch_Index.cpp +34 -37
- package/java/test/IndexTest.java +1 -1
- package/javascript/README.md +6 -6
- package/javascript/lib.cpp +156 -55
- package/javascript/test.js +26 -11
- package/javascript/usearch.d.ts +102 -0
- package/javascript/usearch.js +2 -118
- package/objc/USearchObjective.mm +57 -31
- package/objc/include/USearchObjective.h +32 -22
- package/package.json +5 -3
- package/python/README.md +23 -14
- package/python/lib.cpp +468 -444
- package/python/scripts/bench.ipynb +3 -2
- package/python/scripts/bench.py +4 -4
- package/python/scripts/index_faiss.py +5 -5
- package/python/scripts/join.py +4 -4
- package/python/scripts/test.py +152 -61
- package/python/scripts/test_jit.py +10 -12
- package/python/scripts/test_sparse.py +32 -0
- package/python/usearch/client.py +26 -28
- package/python/usearch/eval.py +73 -23
- package/python/usearch/index.py +520 -228
- package/python/usearch/io.py +57 -25
- package/python/usearch/numba.py +4 -4
- package/python/usearch/server.py +10 -10
- package/rust/lib.cpp +34 -30
- package/rust/lib.hpp +14 -12
- package/rust/lib.rs +8 -7
- package/setup.py +5 -2
- package/simsimd/.github/workflows/prerelease.yml +73 -0
- package/simsimd/.github/workflows/release.yml +110 -0
- package/simsimd/.github/workflows/update_version.sh +3 -0
- package/simsimd/.releaserc +77 -0
- package/simsimd/.vscode/launch.json +27 -1
- package/simsimd/.vscode/settings.json +22 -1
- package/simsimd/.vscode/tasks.json +6 -0
- package/simsimd/CMakeLists.txt +3 -2
- package/simsimd/README.md +82 -46
- package/simsimd/bench.cxx +63 -24
- package/simsimd/include/simsimd/simsimd.h +88 -66
- package/simsimd/include/simsimd/simsimd_chem.h +126 -0
- package/simsimd/package.json +8 -0
- package/simsimd/pyproject.toml +42 -0
- package/simsimd/python/python.c +81 -0
- package/simsimd/python/run.sh +1 -0
- package/simsimd/python/test.c +109 -0
- package/simsimd/python/test.py +23 -0
- package/simsimd/setup.py +44 -0
- package/swift/Index+Sugar.swift +75 -23
- package/swift/README.md +26 -2
- package/swift/Test.swift +19 -7
- package/swift/USearch.swift +2 -2
- package/tsconfig.json +8 -0
- package/wasm/CMakeLists.txt +1 -0
- package/wasmer.toml +1 -1
- package/wolfram/CMakeLists.txt +1 -0
- package/wolfram/lib.cpp +11 -13
- package/golang/usearch.h +0 -9
- package/golang/usearch_par_test.go +0 -40
- package/golang/usearch_test.go +0 -82
- package/include/usearch/index_punned_dense.hpp +0 -1001
- package/include/usearch/index_punned_helpers.hpp +0 -676
package/golang/lib.go
CHANGED
|
@@ -2,52 +2,66 @@ package usearch
|
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
4
|
"errors"
|
|
5
|
-
"fmt"
|
|
6
5
|
"unsafe"
|
|
7
6
|
)
|
|
8
7
|
|
|
9
8
|
/*
|
|
10
|
-
#
|
|
11
|
-
|
|
12
|
-
#
|
|
13
|
-
#
|
|
9
|
+
#cgo CFLAGS: -I${SRCDIR}/../c/
|
|
10
|
+
#cgo LDFLAGS: -L./ -lusearch
|
|
11
|
+
#include <usearch.h>
|
|
12
|
+
#include <stdlib.h>
|
|
14
13
|
*/
|
|
15
14
|
import "C"
|
|
16
15
|
|
|
17
|
-
type
|
|
16
|
+
type Key = uint64
|
|
17
|
+
|
|
18
|
+
type Metric uint8
|
|
18
19
|
|
|
19
20
|
const (
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
InnerProduct Metric = iota
|
|
22
|
+
Cosine
|
|
23
|
+
L2sq
|
|
23
24
|
Haversine
|
|
25
|
+
Pearson
|
|
26
|
+
Hamming
|
|
27
|
+
Tanimoto
|
|
28
|
+
Sorensen
|
|
24
29
|
)
|
|
25
30
|
|
|
26
|
-
func (m
|
|
31
|
+
func (m Metric) String() string {
|
|
27
32
|
switch m {
|
|
28
|
-
case
|
|
33
|
+
case L2sq:
|
|
29
34
|
return "l2sq"
|
|
30
|
-
case
|
|
35
|
+
case InnerProduct:
|
|
31
36
|
return "ip"
|
|
32
|
-
case
|
|
37
|
+
case Cosine:
|
|
33
38
|
return "cos"
|
|
34
39
|
case Haversine:
|
|
35
40
|
return "haversine"
|
|
41
|
+
case Pearson:
|
|
42
|
+
return "pearson"
|
|
43
|
+
case Hamming:
|
|
44
|
+
return "hamming"
|
|
45
|
+
case Tanimoto:
|
|
46
|
+
return "tanimoto"
|
|
47
|
+
case Sorensen:
|
|
48
|
+
return "sorensen"
|
|
36
49
|
default:
|
|
37
|
-
panic("
|
|
50
|
+
panic("Unknown metric")
|
|
38
51
|
}
|
|
39
52
|
}
|
|
40
53
|
|
|
41
|
-
type
|
|
54
|
+
type Quantization uint8
|
|
42
55
|
|
|
43
56
|
const (
|
|
44
|
-
f32
|
|
57
|
+
f32 Quantization = iota
|
|
45
58
|
f16
|
|
46
59
|
f64
|
|
47
|
-
|
|
60
|
+
i8
|
|
61
|
+
b1
|
|
48
62
|
)
|
|
49
63
|
|
|
50
|
-
func (a
|
|
64
|
+
func (a Quantization) String() string {
|
|
51
65
|
switch a {
|
|
52
66
|
case f16:
|
|
53
67
|
return "f16"
|
|
@@ -55,30 +69,38 @@ func (a Accuracy) String() string {
|
|
|
55
69
|
return "f32"
|
|
56
70
|
case f64:
|
|
57
71
|
return "f64"
|
|
58
|
-
case
|
|
59
|
-
return "
|
|
72
|
+
case i8:
|
|
73
|
+
return "i8"
|
|
74
|
+
case b1:
|
|
75
|
+
return "b1"
|
|
60
76
|
default:
|
|
61
|
-
panic("
|
|
77
|
+
panic("Unknown quantization")
|
|
62
78
|
}
|
|
63
79
|
}
|
|
64
80
|
|
|
65
81
|
type IndexConfig struct {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
82
|
+
// The scalar kind used for quantization of vector data during indexing.
|
|
83
|
+
Quantization Quantization
|
|
84
|
+
// The metric kind used for distance calculation between vectors.
|
|
85
|
+
Metric Metric
|
|
86
|
+
// The number of dimensions in the vectors to be indexed.
|
|
87
|
+
Dimensions uint
|
|
88
|
+
// The @b optional connectivity parameter that limits connections-per-node in graph.
|
|
89
|
+
Connectivity uint
|
|
90
|
+
// The @b optional expansion factor used for index construction when adding vectors.
|
|
91
|
+
ExpansionAdd uint
|
|
92
|
+
// The @b optional expansion factor used for index construction during search operations.
|
|
93
|
+
ExpansionSearch uint
|
|
73
94
|
}
|
|
74
95
|
|
|
75
|
-
func DefaultConfig(
|
|
96
|
+
func DefaultConfig(dimensions uint) IndexConfig {
|
|
76
97
|
c := IndexConfig{}
|
|
77
|
-
c.
|
|
78
|
-
c.
|
|
79
|
-
|
|
80
|
-
c.
|
|
81
|
-
c.
|
|
98
|
+
c.Dimensions = dimensions
|
|
99
|
+
c.Metric = Cosine
|
|
100
|
+
// Zeros will be replaced by the underlying C implementation
|
|
101
|
+
c.Connectivity = 0
|
|
102
|
+
c.ExpansionAdd = 0
|
|
103
|
+
c.ExpansionSearch = 0
|
|
82
104
|
return c
|
|
83
105
|
}
|
|
84
106
|
|
|
@@ -87,152 +109,264 @@ type Index struct {
|
|
|
87
109
|
config IndexConfig
|
|
88
110
|
}
|
|
89
111
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
return ind
|
|
94
|
-
}
|
|
112
|
+
// Initializes a new instance of the index.
|
|
113
|
+
func NewIndex(conf IndexConfig) (index *Index, err error) {
|
|
114
|
+
index = &Index{config: conf}
|
|
95
115
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
accuracy_str := C.CString(conf.Accuracy.String())
|
|
102
|
-
defer C.free(unsafe.Pointer(accuracy_str))
|
|
103
|
-
accuracy_len := C.int(len(conf.Accuracy.String()))
|
|
104
|
-
dimensions := C.int(conf.VecDimension)
|
|
105
|
-
capacity := C.int(conf.InitCapacity)
|
|
106
|
-
connectivity := C.int(conf.Connectivity)
|
|
107
|
-
expansion_add := C.int(conf.ExpansionAdd)
|
|
108
|
-
expansion_search := C.int(conf.ExpansionSearch)
|
|
109
|
-
ptr := C.usearch_new(metric_str, metric_len,
|
|
110
|
-
accuracy_str, accuracy_len,
|
|
111
|
-
dimensions, capacity, connectivity,
|
|
112
|
-
expansion_add, expansion_search)
|
|
116
|
+
conf = index.config
|
|
117
|
+
dimensions := C.ulong(conf.Dimensions)
|
|
118
|
+
connectivity := C.ulong(conf.Connectivity)
|
|
119
|
+
expansion_add := C.ulong(conf.ExpansionAdd)
|
|
120
|
+
expansion_search := C.ulong(conf.ExpansionSearch)
|
|
113
121
|
|
|
114
|
-
|
|
115
|
-
|
|
122
|
+
options := C.struct_usearch_init_options_t{}
|
|
123
|
+
options.dimensions = dimensions
|
|
124
|
+
options.connectivity = connectivity
|
|
125
|
+
options.expansion_add = expansion_add
|
|
126
|
+
options.expansion_search = expansion_search
|
|
116
127
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
128
|
+
// Map the metric kind to a C enum
|
|
129
|
+
switch conf.Metric {
|
|
130
|
+
case L2sq:
|
|
131
|
+
options.metric_kind = C.usearch_metric_l2sq_k
|
|
132
|
+
case InnerProduct:
|
|
133
|
+
options.metric_kind = C.usearch_metric_ip_k
|
|
134
|
+
case Cosine:
|
|
135
|
+
options.metric_kind = C.usearch_metric_cos_k
|
|
136
|
+
case Haversine:
|
|
137
|
+
options.metric_kind = C.usearch_metric_haversine_k
|
|
138
|
+
case Pearson:
|
|
139
|
+
options.metric_kind = C.usearch_metric_pearson_k
|
|
140
|
+
case Hamming:
|
|
141
|
+
options.metric_kind = C.usearch_metric_hamming_k
|
|
142
|
+
case Tanimoto:
|
|
143
|
+
options.metric_kind = C.usearch_metric_tanimoto_k
|
|
144
|
+
case Sorensen:
|
|
145
|
+
options.metric_kind = C.usearch_metric_sorensen_k
|
|
146
|
+
default:
|
|
147
|
+
options.metric_kind = C.usearch_metric_unknown_k
|
|
120
148
|
}
|
|
121
|
-
C.usearch_free(unsafe.Pointer(ind.opaque_handle))
|
|
122
|
-
ind.opaque_handle = nil
|
|
123
|
-
ind.config = IndexConfig{}
|
|
124
|
-
}
|
|
125
149
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
case
|
|
135
|
-
|
|
136
|
-
case
|
|
137
|
-
|
|
138
|
-
case "view":
|
|
139
|
-
errStr = C.usearch_view(unsafe.Pointer(ind.opaque_handle), c_path)
|
|
150
|
+
// Map the quantization method
|
|
151
|
+
switch conf.Quantization {
|
|
152
|
+
case f16:
|
|
153
|
+
options.quantization = C.usearch_scalar_f16_k
|
|
154
|
+
case f32:
|
|
155
|
+
options.quantization = C.usearch_scalar_f32_k
|
|
156
|
+
case f64:
|
|
157
|
+
options.quantization = C.usearch_scalar_f64_k
|
|
158
|
+
case i8:
|
|
159
|
+
options.quantization = C.usearch_scalar_i8_k
|
|
160
|
+
case b1:
|
|
161
|
+
options.quantization = C.usearch_scalar_b1_k
|
|
140
162
|
default:
|
|
141
|
-
|
|
163
|
+
options.quantization = C.usearch_scalar_unknown_k
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
var errorMessage *C.char
|
|
167
|
+
ptr := C.usearch_init(&options, (*C.usearch_error_t)(&errorMessage))
|
|
168
|
+
if errorMessage != nil {
|
|
169
|
+
return nil, errors.New(C.GoString(errorMessage))
|
|
142
170
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
171
|
+
|
|
172
|
+
index.opaque_handle = (*C.void)(unsafe.Pointer(ptr))
|
|
173
|
+
return index, nil
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
func (index *Index) Len() (len uint, err error) {
|
|
177
|
+
var errorMessage *C.char
|
|
178
|
+
len = uint(C.usearch_size((C.usearch_index_t)(unsafe.Pointer(index.opaque_handle)), (*C.usearch_error_t)(&errorMessage)))
|
|
179
|
+
if errorMessage != nil {
|
|
180
|
+
err = errors.New(C.GoString(errorMessage))
|
|
181
|
+
}
|
|
182
|
+
return len, err
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
func (index *Index) Connectivity() (con uint, err error) {
|
|
186
|
+
var errorMessage *C.char
|
|
187
|
+
con = uint(C.usearch_connectivity((C.usearch_index_t)(unsafe.Pointer(index.opaque_handle)), (*C.usearch_error_t)(&errorMessage)))
|
|
188
|
+
if errorMessage != nil {
|
|
189
|
+
err = errors.New(C.GoString(errorMessage))
|
|
147
190
|
}
|
|
148
|
-
return err
|
|
191
|
+
return con, err
|
|
149
192
|
}
|
|
150
193
|
|
|
151
|
-
func (
|
|
152
|
-
|
|
194
|
+
func (index *Index) Dimensions() (dim uint, err error) {
|
|
195
|
+
var errorMessage *C.char
|
|
196
|
+
dim = uint(C.usearch_dimensions((C.usearch_index_t)(unsafe.Pointer(index.opaque_handle)), (*C.usearch_error_t)(&errorMessage)))
|
|
197
|
+
if errorMessage != nil {
|
|
198
|
+
err = errors.New(C.GoString(errorMessage))
|
|
199
|
+
}
|
|
200
|
+
return dim, err
|
|
153
201
|
}
|
|
154
202
|
|
|
155
|
-
func (
|
|
156
|
-
|
|
203
|
+
func (index *Index) Capacity() (cap uint, err error) {
|
|
204
|
+
var errorMessage *C.char
|
|
205
|
+
cap = uint(C.usearch_capacity((C.usearch_index_t)(unsafe.Pointer(index.opaque_handle)), (*C.usearch_error_t)(&errorMessage)))
|
|
206
|
+
if errorMessage != nil {
|
|
207
|
+
err = errors.New(C.GoString(errorMessage))
|
|
208
|
+
}
|
|
209
|
+
return cap, err
|
|
157
210
|
}
|
|
158
211
|
|
|
159
|
-
|
|
160
|
-
|
|
212
|
+
// Frees the resources associated with the index.
|
|
213
|
+
func (index *Index) Destroy() error {
|
|
214
|
+
if index.opaque_handle == nil {
|
|
215
|
+
panic("Index is uninitialized")
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
var errorMessage *C.char
|
|
219
|
+
C.usearch_free((C.usearch_index_t)(unsafe.Pointer(index.opaque_handle)), (*C.usearch_error_t)(&errorMessage))
|
|
220
|
+
if errorMessage != nil {
|
|
221
|
+
return errors.New(C.GoString(errorMessage))
|
|
222
|
+
}
|
|
223
|
+
index.opaque_handle = nil
|
|
224
|
+
index.config = IndexConfig{}
|
|
225
|
+
return nil
|
|
161
226
|
}
|
|
162
227
|
|
|
163
|
-
|
|
164
|
-
|
|
228
|
+
// Reserves memory for a specified number of incoming vectors.
|
|
229
|
+
func (index *Index) Reserve(capacity uint) error {
|
|
230
|
+
if index.opaque_handle == nil {
|
|
231
|
+
panic("Index is uninitialized")
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
var errorMessage *C.char
|
|
235
|
+
C.usearch_reserve((C.usearch_index_t)(unsafe.Pointer(index.opaque_handle)), (C.size_t)(capacity), (*C.usearch_error_t)(&errorMessage))
|
|
236
|
+
if errorMessage != nil {
|
|
237
|
+
return errors.New(C.GoString(errorMessage))
|
|
238
|
+
}
|
|
239
|
+
return nil
|
|
165
240
|
}
|
|
166
241
|
|
|
167
|
-
|
|
168
|
-
|
|
242
|
+
// Adds a vector with a key to the index.
|
|
243
|
+
func (index *Index) Add(key Key, vec []float32) error {
|
|
244
|
+
if index.opaque_handle == nil {
|
|
245
|
+
panic("Index is uninitialized")
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
var errorMessage *C.char
|
|
249
|
+
C.usearch_add((C.usearch_index_t)(unsafe.Pointer(index.opaque_handle)), (C.usearch_key_t)(key), unsafe.Pointer(&vec[0]), C.usearch_scalar_f32_k, (*C.usearch_error_t)(&errorMessage))
|
|
250
|
+
if errorMessage != nil {
|
|
251
|
+
return errors.New(C.GoString(errorMessage))
|
|
252
|
+
}
|
|
253
|
+
return nil
|
|
169
254
|
}
|
|
170
255
|
|
|
171
|
-
|
|
172
|
-
|
|
256
|
+
// Removes the vector associated with the given key from the index.
|
|
257
|
+
func (index *Index) Remove(key Key) error {
|
|
258
|
+
if index.opaque_handle == nil {
|
|
259
|
+
panic("Index is uninitialized")
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
var errorMessage *C.char
|
|
263
|
+
C.usearch_remove((C.usearch_index_t)(unsafe.Pointer(index.opaque_handle)), (C.usearch_key_t)(key), (*C.usearch_error_t)(&errorMessage))
|
|
264
|
+
if errorMessage != nil {
|
|
265
|
+
return errors.New(C.GoString(errorMessage))
|
|
266
|
+
}
|
|
267
|
+
return nil
|
|
173
268
|
}
|
|
174
269
|
|
|
175
|
-
|
|
176
|
-
|
|
270
|
+
// Checks if the index contains a vector with a specific key.
|
|
271
|
+
func (index *Index) Contains(key Key) (found bool, err error) {
|
|
272
|
+
if index.opaque_handle == nil {
|
|
273
|
+
panic("Index is uninitialized")
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
var errorMessage *C.char
|
|
277
|
+
found = bool(C.usearch_contains((C.usearch_index_t)(unsafe.Pointer(index.opaque_handle)), (C.usearch_key_t)(key), (*C.usearch_error_t)(&errorMessage)))
|
|
278
|
+
if errorMessage != nil {
|
|
279
|
+
return found, errors.New(C.GoString(errorMessage))
|
|
280
|
+
}
|
|
281
|
+
return found, nil
|
|
177
282
|
}
|
|
178
283
|
|
|
179
|
-
func (
|
|
180
|
-
if
|
|
181
|
-
panic("
|
|
284
|
+
func (index *Index) Get(key Key) (vector []float32, err error) {
|
|
285
|
+
if index.opaque_handle == nil {
|
|
286
|
+
panic("Index is uninitialized")
|
|
182
287
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
288
|
+
|
|
289
|
+
vector = make([]float32, index.config.Dimensions)
|
|
290
|
+
var errorMessage *C.char
|
|
291
|
+
found := bool(C.usearch_get((C.usearch_index_t)(unsafe.Pointer(index.opaque_handle)), (C.usearch_key_t)(key), unsafe.Pointer(&vector[0]), C.usearch_scalar_f32_k, (*C.usearch_error_t)(&errorMessage)))
|
|
292
|
+
if errorMessage != nil {
|
|
293
|
+
return nil, errors.New(C.GoString(errorMessage))
|
|
188
294
|
}
|
|
189
|
-
|
|
295
|
+
if !found {
|
|
296
|
+
return nil, nil
|
|
297
|
+
}
|
|
298
|
+
return vector, nil
|
|
190
299
|
}
|
|
191
300
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
301
|
+
// Performs k-Approximate Nearest Neighbors Search for closest vectors to query.
|
|
302
|
+
func (index *Index) Search(query []float32, limit uint) (keys []Key, distances []float32, err error) {
|
|
303
|
+
if index.opaque_handle == nil {
|
|
304
|
+
panic("Index is uninitialized")
|
|
305
|
+
}
|
|
306
|
+
if len(query) != int(index.config.Dimensions) {
|
|
307
|
+
return nil, nil, errors.New("Number of dimensions doesn't match!")
|
|
195
308
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
309
|
+
|
|
310
|
+
keys = make([]Key, limit)
|
|
311
|
+
distances = make([]float32, limit)
|
|
312
|
+
var errorMessage *C.char
|
|
313
|
+
count := uint(C.usearch_search((C.usearch_index_t)(unsafe.Pointer(index.opaque_handle)), unsafe.Pointer(&query[0]), C.usearch_scalar_f32_k, (C.size_t)(limit), (*C.usearch_key_t)(&keys[0]), (*C.usearch_distance_t)(&distances[0]), (*C.usearch_error_t)(&errorMessage)))
|
|
314
|
+
if errorMessage != nil {
|
|
315
|
+
return nil, nil, errors.New(C.GoString(errorMessage))
|
|
201
316
|
}
|
|
202
|
-
return err
|
|
203
317
|
|
|
318
|
+
keys = keys[:count]
|
|
319
|
+
distances = distances[:count]
|
|
320
|
+
return keys, distances, nil
|
|
204
321
|
}
|
|
205
322
|
|
|
206
|
-
//
|
|
207
|
-
func (
|
|
208
|
-
if
|
|
209
|
-
panic("
|
|
323
|
+
// Saves the index to a file.
|
|
324
|
+
func (index *Index) Save(path string) error {
|
|
325
|
+
if index.opaque_handle == nil {
|
|
326
|
+
panic("Index is uninitialized")
|
|
210
327
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
328
|
+
|
|
329
|
+
c_path := C.CString(path)
|
|
330
|
+
defer C.free(unsafe.Pointer(c_path))
|
|
331
|
+
|
|
332
|
+
var errorMessage *C.char
|
|
333
|
+
C.usearch_save((C.usearch_index_t)(unsafe.Pointer(index.opaque_handle)), c_path, (*C.usearch_error_t)(&errorMessage))
|
|
334
|
+
if errorMessage != nil {
|
|
335
|
+
return errors.New(C.GoString(errorMessage))
|
|
214
336
|
}
|
|
215
|
-
|
|
216
|
-
|
|
337
|
+
return nil
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Loads the index from a file.
|
|
341
|
+
func (index *Index) Load(path string) error {
|
|
342
|
+
if index.opaque_handle == nil {
|
|
343
|
+
panic("Index is uninitialized")
|
|
217
344
|
}
|
|
218
|
-
res := C.usearch_search(unsafe.Pointer(ind.opaque_handle),
|
|
219
|
-
(*C.float)(&query[0]), (C.int)(len(query)), (C.int)(limit))
|
|
220
345
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
346
|
+
c_path := C.CString(path)
|
|
347
|
+
defer C.free(unsafe.Pointer(c_path))
|
|
348
|
+
|
|
349
|
+
var errorMessage *C.char
|
|
350
|
+
C.usearch_load((C.usearch_index_t)(unsafe.Pointer(index.opaque_handle)), c_path, (*C.usearch_error_t)(&errorMessage))
|
|
351
|
+
if errorMessage != nil {
|
|
352
|
+
return errors.New(C.GoString(errorMessage))
|
|
226
353
|
}
|
|
227
|
-
|
|
228
|
-
//q:: who free's this memory? will golang do it?
|
|
229
|
-
labs = unsafe.Slice((*int32)(unsafe.Pointer(res.Labels)), res.Len)
|
|
230
|
-
return labs
|
|
354
|
+
return nil
|
|
231
355
|
}
|
|
232
356
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
357
|
+
// Creates a view of the index from a file without loading it into memory.
|
|
358
|
+
func (index *Index) View(path string) error {
|
|
359
|
+
if index.opaque_handle == nil {
|
|
360
|
+
panic("Index is uninitialized")
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
c_path := C.CString(path)
|
|
364
|
+
defer C.free(unsafe.Pointer(c_path))
|
|
365
|
+
|
|
366
|
+
var errorMessage *C.char
|
|
367
|
+
C.usearch_view((C.usearch_index_t)(unsafe.Pointer(index.opaque_handle)), c_path, (*C.usearch_error_t)(&errorMessage))
|
|
368
|
+
if errorMessage != nil {
|
|
369
|
+
return errors.New(C.GoString(errorMessage))
|
|
236
370
|
}
|
|
237
|
-
return
|
|
371
|
+
return nil
|
|
238
372
|
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
package usearch
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"runtime"
|
|
5
|
+
"testing"
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
func TestUSearch(t *testing.T) {
|
|
9
|
+
runtime.LockOSThread()
|
|
10
|
+
|
|
11
|
+
t.Run("Test Index Initialization", func(t *testing.T) {
|
|
12
|
+
dim := uint(128)
|
|
13
|
+
conf := DefaultConfig(dim)
|
|
14
|
+
ind, err := NewIndex(conf)
|
|
15
|
+
if err != nil {
|
|
16
|
+
t.Fatalf("Failed to construct the index: %s", err)
|
|
17
|
+
}
|
|
18
|
+
defer ind.Destroy()
|
|
19
|
+
|
|
20
|
+
found_dims, err := ind.Dimensions()
|
|
21
|
+
if err != nil {
|
|
22
|
+
t.Fatalf("Failed to retrieve dimensions: %s", err)
|
|
23
|
+
}
|
|
24
|
+
if found_dims != dim {
|
|
25
|
+
t.Fatalf("Expected %d dimensions, got %d", dim, found_dims)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
found_len, err := ind.Len()
|
|
29
|
+
if err != nil {
|
|
30
|
+
t.Fatalf("Failed to retrieve size: %s", err)
|
|
31
|
+
}
|
|
32
|
+
if found_len != 0 {
|
|
33
|
+
t.Fatalf("Expected size to be 0, got %d", found_len)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
err = ind.Reserve(100)
|
|
37
|
+
if err != nil {
|
|
38
|
+
t.Fatalf("Failed to reserve capacity: %s", err)
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
t.Run("Test Insertion", func(t *testing.T) {
|
|
43
|
+
dim := uint(128)
|
|
44
|
+
conf := DefaultConfig(dim)
|
|
45
|
+
ind, err := NewIndex(conf)
|
|
46
|
+
if err != nil {
|
|
47
|
+
t.Fatalf("Failed to construct the index: %s", err)
|
|
48
|
+
}
|
|
49
|
+
defer ind.Destroy()
|
|
50
|
+
|
|
51
|
+
err = ind.Reserve(100)
|
|
52
|
+
if err != nil {
|
|
53
|
+
t.Fatalf("Failed to reserve capacity: %s", err)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
vec := make([]float32, dim)
|
|
57
|
+
vec[0] = 40.0
|
|
58
|
+
vec[1] = 2.0
|
|
59
|
+
|
|
60
|
+
err = ind.Add(42, vec)
|
|
61
|
+
if err != nil {
|
|
62
|
+
t.Fatalf("Failed to insert: %s", err)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
found_len, err := ind.Len()
|
|
66
|
+
if err != nil {
|
|
67
|
+
t.Fatalf("Failed to retrieve size after insertion: %s", err)
|
|
68
|
+
}
|
|
69
|
+
if found_len != 1 {
|
|
70
|
+
t.Fatalf("Expected size to be 1, got %d", found_len)
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
t.Run("Test Search", func(t *testing.T) {
|
|
75
|
+
dim := uint(128)
|
|
76
|
+
conf := DefaultConfig(dim)
|
|
77
|
+
ind, err := NewIndex(conf)
|
|
78
|
+
if err != nil {
|
|
79
|
+
t.Fatalf("Failed to construct the index: %s", err)
|
|
80
|
+
}
|
|
81
|
+
defer ind.Destroy()
|
|
82
|
+
|
|
83
|
+
err = ind.Reserve(100)
|
|
84
|
+
if err != nil {
|
|
85
|
+
t.Fatalf("Failed to reserve capacity: %s", err)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
vec := make([]float32, dim)
|
|
89
|
+
vec[0] = 40.0
|
|
90
|
+
vec[1] = 2.0
|
|
91
|
+
|
|
92
|
+
err = ind.Add(42, vec)
|
|
93
|
+
if err != nil {
|
|
94
|
+
t.Fatalf("Failed to insert: %s", err)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
keys, distances, err := ind.Search(vec, 10)
|
|
98
|
+
if err != nil {
|
|
99
|
+
t.Fatalf("Failed to search: %s", err)
|
|
100
|
+
}
|
|
101
|
+
if keys[0] != 42 || distances[0] != 0.0 {
|
|
102
|
+
t.Fatalf("Expected result 42 with distance 0, got key %d with distance %f", keys[0], distances[0])
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
}
|