usearch 0.22.2 → 1.0.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.
Files changed (96) hide show
  1. package/.github/workflows/prerelease.yml +3 -3
  2. package/.releaserc +9 -0
  3. package/.vscode/launch.json +15 -11
  4. package/.vscode/settings.json +12 -2
  5. package/.vscode/tasks.json +4 -4
  6. package/CMakeLists.txt +6 -1
  7. package/Cargo.toml +2 -2
  8. package/README.md +39 -46
  9. package/build.rs +2 -2
  10. package/c/CMakeLists.txt +48 -0
  11. package/c/README.md +34 -0
  12. package/c/lib.cpp +83 -72
  13. package/c/test.c +20 -20
  14. package/c/usearch.h +132 -32
  15. package/conanfile.py +1 -1
  16. package/cpp/CMakeLists.txt +3 -1
  17. package/cpp/README.md +16 -16
  18. package/cpp/bench.cpp +50 -126
  19. package/cpp/test.cpp +164 -77
  20. package/docs/benchmarks.md +26 -5
  21. package/docs/c/reference.rst +3 -3
  22. package/docs/compilation.md +49 -45
  23. package/docs/cpp/index.rst +2 -2
  24. package/docs/cpp/reference.rst +13 -3
  25. package/docs/index.rst +8 -8
  26. package/docs/java/reference.rst +1 -1
  27. package/golang/lib.go +275 -141
  28. package/golang/lib_test.go +105 -0
  29. package/include/usearch/index.hpp +1733 -1659
  30. package/include/usearch/index_dense.hpp +1400 -0
  31. package/include/usearch/index_plugins.hpp +1263 -0
  32. package/java/README.md +1 -1
  33. package/java/cloud/unum/usearch/Index.java +10 -10
  34. package/java/cloud/unum/usearch/cloud_unum_usearch_Index.cpp +34 -37
  35. package/java/test/IndexTest.java +1 -1
  36. package/javascript/README.md +7 -7
  37. package/javascript/lib.cpp +94 -55
  38. package/javascript/test.js +25 -11
  39. package/javascript/usearch.d.ts +87 -0
  40. package/javascript/usearch.js +2 -118
  41. package/objc/USearchObjective.mm +57 -31
  42. package/objc/include/USearchObjective.h +32 -22
  43. package/package.json +5 -3
  44. package/pyproject.toml +3 -3
  45. package/python/README.md +23 -14
  46. package/python/lib.cpp +472 -444
  47. package/python/scripts/bench.ipynb +3 -2
  48. package/python/scripts/bench.py +4 -4
  49. package/python/scripts/index_faiss.py +5 -5
  50. package/python/scripts/join.py +4 -4
  51. package/python/scripts/test.py +152 -61
  52. package/python/scripts/test_jit.py +10 -12
  53. package/python/scripts/test_sparse.py +32 -0
  54. package/python/usearch/client.py +26 -28
  55. package/python/usearch/eval.py +73 -23
  56. package/python/usearch/index.py +520 -228
  57. package/python/usearch/io.py +57 -25
  58. package/python/usearch/numba.py +4 -4
  59. package/python/usearch/server.py +10 -10
  60. package/rust/lib.cpp +22 -27
  61. package/rust/lib.hpp +11 -12
  62. package/rust/lib.rs +3 -3
  63. package/setup.py +5 -2
  64. package/simsimd/.github/workflows/prerelease.yml +73 -0
  65. package/simsimd/.github/workflows/release.yml +110 -0
  66. package/simsimd/.github/workflows/update_version.sh +3 -0
  67. package/simsimd/.releaserc +77 -0
  68. package/simsimd/.vscode/launch.json +27 -1
  69. package/simsimd/.vscode/settings.json +22 -1
  70. package/simsimd/.vscode/tasks.json +6 -0
  71. package/simsimd/CMakeLists.txt +3 -2
  72. package/simsimd/README.md +82 -46
  73. package/simsimd/bench.cxx +63 -24
  74. package/simsimd/include/simsimd/simsimd.h +88 -66
  75. package/simsimd/include/simsimd/simsimd_chem.h +126 -0
  76. package/simsimd/package.json +8 -0
  77. package/simsimd/pyproject.toml +42 -0
  78. package/simsimd/python/python.c +81 -0
  79. package/simsimd/python/run.sh +1 -0
  80. package/simsimd/python/test.c +109 -0
  81. package/simsimd/python/test.py +23 -0
  82. package/simsimd/setup.py +44 -0
  83. package/swift/Index+Sugar.swift +75 -23
  84. package/swift/README.md +26 -2
  85. package/swift/Test.swift +19 -7
  86. package/swift/USearch.swift +2 -2
  87. package/tsconfig.json +8 -0
  88. package/wasm/CMakeLists.txt +1 -0
  89. package/wasmer.toml +5 -1
  90. package/wolfram/CMakeLists.txt +1 -0
  91. package/wolfram/lib.cpp +11 -13
  92. package/golang/usearch.h +0 -9
  93. package/golang/usearch_par_test.go +0 -40
  94. package/golang/usearch_test.go +0 -82
  95. package/include/usearch/index_punned_dense.hpp +0 -871
  96. package/include/usearch/index_punned_helpers.hpp +0 -566
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
- #include "../c/usearch.h"
11
-
12
- #cgo CPPFLAGS: -I.
13
- #cgo LDFLAGS: -lusearch
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 DistMetric int8
16
+ type Key = uint64
17
+
18
+ type Metric uint8
18
19
 
19
20
  const (
20
- L2_SQ DistMetric = iota
21
- IP
22
- COS
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 DistMetric) String() string {
31
+ func (m Metric) String() string {
27
32
  switch m {
28
- case L2_SQ:
33
+ case L2sq:
29
34
  return "l2sq"
30
- case IP:
35
+ case InnerProduct:
31
36
  return "ip"
32
- case COS:
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("unknown metric")
50
+ panic("Unknown metric")
38
51
  }
39
52
  }
40
53
 
41
- type Accuracy int8
54
+ type Quantization uint8
42
55
 
43
56
  const (
44
- f32 Accuracy = iota
57
+ f32 Quantization = iota
45
58
  f16
46
59
  f64
47
- f8
60
+ i8
61
+ b1
48
62
  )
49
63
 
50
- func (a Accuracy) String() string {
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 f8:
59
- return "f8"
72
+ case i8:
73
+ return "i8"
74
+ case b1:
75
+ return "b1"
60
76
  default:
61
- panic("unknown accuracy")
77
+ panic("Unknown quantization")
62
78
  }
63
79
  }
64
80
 
65
81
  type IndexConfig struct {
66
- Accuracy Accuracy
67
- Metric DistMetric
68
- VecDimension int
69
- InitCapacity int
70
- Connectivity int
71
- ExpansionAdd int
72
- ExpansionSearch int
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(dimension int) IndexConfig {
96
+ func DefaultConfig(dimensions uint) IndexConfig {
76
97
  c := IndexConfig{}
77
- c.InitCapacity = 32
78
- c.Connectivity = 16
79
- c.VecDimension = dimension
80
- c.ExpansionAdd = 128
81
- c.ExpansionSearch = 64
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
- func NewIndex(conf IndexConfig) *Index {
91
- ind := &Index{config: conf}
92
- ind.init()
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
- func (ind *Index) init() {
97
- conf := ind.config
98
- metric_str := C.CString(conf.Metric.String())
99
- defer C.free(unsafe.Pointer(metric_str))
100
- metric_len := C.int(len(conf.Metric.String()))
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
- ind.opaque_handle = (*C.void)(unsafe.Pointer(ptr))
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
- func (ind *Index) Destroy() {
118
- if ind.opaque_handle == nil {
119
- panic("index not initialized")
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
- func (ind *Index) fileOp(path string, op string) error {
127
- if ind.opaque_handle == nil {
128
- panic("index not initialized")
129
- }
130
- c_path := C.CString(path)
131
- defer C.free(unsafe.Pointer(c_path))
132
- var errStr *C.char
133
- switch op {
134
- case "load":
135
- errStr = C.usearch_load(unsafe.Pointer(ind.opaque_handle), c_path)
136
- case "save":
137
- errStr = C.usearch_save(unsafe.Pointer(ind.opaque_handle), c_path)
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
- panic("unknown file operation")
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
- var err error
144
- if errStr != nil {
145
- err = errors.New(C.GoString(errStr))
146
- C.free(unsafe.Pointer(errStr))
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 (ind *Index) Save(path string) error {
152
- return ind.fileOp(path, "save")
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 (ind *Index) Load(path string) error {
156
- return ind.fileOp(path, "load")
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
- func (ind *Index) View(path string) error {
160
- return ind.fileOp(path, "view")
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
- func (ind *Index) Len() int {
164
- return int(C.usearch_size(unsafe.Pointer(ind.opaque_handle)))
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
- func (ind *Index) Connectivity() int {
168
- return int(C.usearch_connectivity(unsafe.Pointer(ind.opaque_handle)))
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
- func (ind *Index) VecDimension() int {
172
- return int(C.usearch_dimensions(unsafe.Pointer(ind.opaque_handle)))
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
- func (ind *Index) Capacity() int {
176
- return int(C.usearch_capacity(unsafe.Pointer(ind.opaque_handle)))
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 (ind *Index) Reserve(capacity int) error {
180
- if ind.opaque_handle == nil {
181
- panic("index not initialized")
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
- errStr := C.usearch_reserve(unsafe.Pointer(ind.opaque_handle), (C.int)(capacity))
184
- var err error
185
- if errStr != nil {
186
- err = errors.New(C.GoString(errStr))
187
- C.free(unsafe.Pointer(errStr))
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
- return err
295
+ if !found {
296
+ return nil, nil
297
+ }
298
+ return vector, nil
190
299
  }
191
300
 
192
- func (ind *Index) Add(label int, vec []float32) error {
193
- if ind.opaque_handle == nil {
194
- panic("index not initialized")
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
- errStr := C.usearch_add(unsafe.Pointer(ind.opaque_handle), (C.int)(label), (*C.float)(&vec[0]))
197
- var err error
198
- if errStr != nil {
199
- err = errors.New(C.GoString(errStr))
200
- C.free(unsafe.Pointer(errStr))
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
- // return must be int32 because int is 64bit in golang and 32bit in C
207
- func (ind *Index) Search(query []float32, limit int) []int32 {
208
- if ind.opaque_handle == nil {
209
- panic("index not initialized")
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
- if len(query) != ind.config.VecDimension {
212
- panic(fmt.Sprintf("query vector dimension mismatch. expected %d, got %d",
213
- ind.config.VecDimension, len(query)))
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
- if limit <= 0 {
216
- panic("limit must be greater than 0")
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
- // my understanding is that search panics in truly exceptional cases,
222
- // none of which is every expected. so, not passing the error to the caller
223
- if res.Error != nil {
224
- defer C.free(unsafe.Pointer(res.Error))
225
- panic(C.GoString(res.Error))
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
- var labs []int32
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
- func (ind *Index) Size() int {
234
- if ind.opaque_handle == nil {
235
- panic("index not initialized")
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 int(C.usearch_size(unsafe.Pointer(ind.opaque_handle)))
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
+ }