neighbor 0.4.0 → 0.4.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +339 -37
- data/lib/neighbor/model.rb +14 -1
- data/lib/neighbor/railtie.rb +1 -1
- data/lib/neighbor/type/cube.rb +4 -3
- data/lib/neighbor/type/halfvec.rb +4 -4
- data/lib/neighbor/type/sparsevec.rb +2 -2
- data/lib/neighbor/type/vector.rb +4 -4
- data/lib/neighbor/utils.rb +4 -0
- data/lib/neighbor/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dfc4af6302c7098ea40f96e9d8a19706aff46a2506cad541ff18ee07fcd11019
|
4
|
+
data.tar.gz: a79b59895ca3b99a7c048eddd20cb3602b2660425ab44463e7021ab763a26f62
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 11081e687de4c79428351095477137f9140bc6c0363d09c54ece8fd5f7bbe2df802d740332f4474357f9e9e57157bd1f1f4dd3671c106d24dc7e01e2f0d84e2a
|
7
|
+
data.tar.gz: f18d787b22df7bbc00c69b1f9f6262e19c0214f6ef28814a5c05d9e8c3dae357f32596994bef60112d3f53dac0c1b51f6c99af6345c7e01b2f26cef1a7b42226
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
## 0.4.2 (2024-08-27)
|
2
|
+
|
3
|
+
- Fixed error with `nil` values
|
4
|
+
|
5
|
+
## 0.4.1 (2024-08-26)
|
6
|
+
|
7
|
+
- Added `precision` option
|
8
|
+
- Added support for `bit` dimensions to model generator
|
9
|
+
- Fixed error with Numo arrays
|
10
|
+
|
1
11
|
## 0.4.0 (2024-06-25)
|
2
12
|
|
3
13
|
- Added support for `halfvec` and `sparsevec` types
|
data/README.md
CHANGED
@@ -14,7 +14,7 @@ gem "neighbor"
|
|
14
14
|
|
15
15
|
## Choose An Extension
|
16
16
|
|
17
|
-
Neighbor supports two extensions: [cube](https://www.postgresql.org/docs/current/cube.html) and [
|
17
|
+
Neighbor supports two extensions: [cube](https://www.postgresql.org/docs/current/cube.html) and [pgvector](https://github.com/pgvector/pgvector). cube ships with Postgres, while pgvector supports more dimensions and approximate nearest neighbor search.
|
18
18
|
|
19
19
|
For cube, run:
|
20
20
|
|
@@ -23,7 +23,7 @@ rails generate neighbor:cube
|
|
23
23
|
rails db:migrate
|
24
24
|
```
|
25
25
|
|
26
|
-
For
|
26
|
+
For pgvector, [install the extension](https://github.com/pgvector/pgvector#installation) and run:
|
27
27
|
|
28
28
|
```sh
|
29
29
|
rails generate neighbor:vector
|
@@ -35,7 +35,7 @@ rails db:migrate
|
|
35
35
|
Create a migration
|
36
36
|
|
37
37
|
```ruby
|
38
|
-
class AddEmbeddingToItems < ActiveRecord::Migration[7.
|
38
|
+
class AddEmbeddingToItems < ActiveRecord::Migration[7.2]
|
39
39
|
def change
|
40
40
|
add_column :items, :embedding, :cube
|
41
41
|
# or
|
@@ -70,15 +70,30 @@ Get the nearest neighbors to a vector
|
|
70
70
|
Item.nearest_neighbors(:embedding, [0.9, 1.3, 1.1], distance: "euclidean").first(5)
|
71
71
|
```
|
72
72
|
|
73
|
-
|
73
|
+
Records returned from `nearest_neighbors` will have a `neighbor_distance` attribute
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
nearest_item = item.nearest_neighbors(:embedding, distance: "euclidean").first
|
77
|
+
nearest_item.neighbor_distance
|
78
|
+
```
|
79
|
+
|
80
|
+
See the additional docs for:
|
81
|
+
|
82
|
+
- [cube](#cube)
|
83
|
+
- [pgvector](#pgvector)
|
84
|
+
|
85
|
+
Or check out some [examples](#examples)
|
86
|
+
|
87
|
+
## cube
|
88
|
+
|
89
|
+
### Distance
|
74
90
|
|
75
91
|
Supported values are:
|
76
92
|
|
77
93
|
- `euclidean`
|
78
94
|
- `cosine`
|
79
|
-
- `taxicab`
|
80
|
-
- `chebyshev`
|
81
|
-
- `inner_product` (vector only)
|
95
|
+
- `taxicab`
|
96
|
+
- `chebyshev`
|
82
97
|
|
83
98
|
For cosine distance with cube, vectors must be normalized before being stored.
|
84
99
|
|
@@ -88,18 +103,11 @@ class Item < ApplicationRecord
|
|
88
103
|
end
|
89
104
|
```
|
90
105
|
|
91
|
-
For inner product with cube, see [this example](examples/
|
106
|
+
For inner product with cube, see [this example](examples/disco/user_recs_cube.rb).
|
92
107
|
|
93
|
-
|
108
|
+
### Dimensions
|
94
109
|
|
95
|
-
|
96
|
-
nearest_item = item.nearest_neighbors(:embedding, distance: "euclidean").first
|
97
|
-
nearest_item.neighbor_distance
|
98
|
-
```
|
99
|
-
|
100
|
-
## Dimensions
|
101
|
-
|
102
|
-
The cube data type can have up to 100 dimensions by default. See the [Postgres docs](https://www.postgresql.org/docs/current/cube.html) for how to increase this. The vector data type can have up to 16,000 dimensions, and vectors with up to 2,000 dimensions can be indexed.
|
110
|
+
The `cube` type can have up to 100 dimensions by default. See the [Postgres docs](https://www.postgresql.org/docs/current/cube.html) for how to increase this.
|
103
111
|
|
104
112
|
For cube, it’s a good idea to specify the number of dimensions to ensure all records have the same number.
|
105
113
|
|
@@ -109,12 +117,29 @@ class Item < ApplicationRecord
|
|
109
117
|
end
|
110
118
|
```
|
111
119
|
|
112
|
-
##
|
120
|
+
## pgvector
|
121
|
+
|
122
|
+
### Distance
|
123
|
+
|
124
|
+
Supported values are:
|
125
|
+
|
126
|
+
- `euclidean`
|
127
|
+
- `inner_product`
|
128
|
+
- `cosine`
|
129
|
+
- `taxicab`
|
130
|
+
- `hamming`
|
131
|
+
- `jaccard`
|
113
132
|
|
114
|
-
|
133
|
+
### Dimensions
|
134
|
+
|
135
|
+
The `vector` type can have up to 16,000 dimensions, and vectors with up to 2,000 dimensions can be indexed.
|
136
|
+
|
137
|
+
### Indexing
|
138
|
+
|
139
|
+
Add an approximate index to speed up queries. Create a migration with:
|
115
140
|
|
116
141
|
```ruby
|
117
|
-
class AddIndexToItemsEmbedding < ActiveRecord::Migration[7.
|
142
|
+
class AddIndexToItemsEmbedding < ActiveRecord::Migration[7.2]
|
118
143
|
def change
|
119
144
|
add_index :items, :embedding, using: :hnsw, opclass: :vector_l2_ops
|
120
145
|
# or
|
@@ -137,9 +162,91 @@ Or the number of probes with IVFFlat
|
|
137
162
|
Item.connection.execute("SET ivfflat.probes = 3")
|
138
163
|
```
|
139
164
|
|
165
|
+
### Half-Precision Vectors
|
166
|
+
|
167
|
+
Use the `halfvec` type to store half-precision vectors
|
168
|
+
|
169
|
+
```ruby
|
170
|
+
class AddEmbeddingToItems < ActiveRecord::Migration[7.2]
|
171
|
+
def change
|
172
|
+
add_column :items, :embedding, :halfvec, limit: 3 # dimensions
|
173
|
+
end
|
174
|
+
end
|
175
|
+
```
|
176
|
+
|
177
|
+
### Half-Precision Indexing
|
178
|
+
|
179
|
+
Index vectors at half precision for smaller indexes
|
180
|
+
|
181
|
+
```ruby
|
182
|
+
class AddIndexToItemsEmbedding < ActiveRecord::Migration[7.2]
|
183
|
+
def change
|
184
|
+
add_index :items, "(embedding::halfvec(3)) vector_l2_ops", using: :hnsw
|
185
|
+
end
|
186
|
+
end
|
187
|
+
```
|
188
|
+
|
189
|
+
Get the nearest neighbors
|
190
|
+
|
191
|
+
```ruby
|
192
|
+
Item.nearest_neighbors(:embedding, [0.9, 1.3, 1.1], distance: "euclidean", precision: "half").first(5)
|
193
|
+
```
|
194
|
+
|
195
|
+
### Binary Vectors
|
196
|
+
|
197
|
+
Use the `bit` type to store binary vectors
|
198
|
+
|
199
|
+
```ruby
|
200
|
+
class AddEmbeddingToItems < ActiveRecord::Migration[7.2]
|
201
|
+
def change
|
202
|
+
add_column :items, :embedding, :bit, limit: 3 # dimensions
|
203
|
+
end
|
204
|
+
end
|
205
|
+
```
|
206
|
+
|
207
|
+
Get the nearest neighbors by Hamming distance
|
208
|
+
|
209
|
+
```ruby
|
210
|
+
Item.nearest_neighbors(:embedding, "101", distance: "hamming").first(5)
|
211
|
+
```
|
212
|
+
|
213
|
+
### Binary Quantization
|
214
|
+
|
215
|
+
Use expression indexing for binary quantization
|
216
|
+
|
217
|
+
```ruby
|
218
|
+
class AddIndexToItemsEmbedding < ActiveRecord::Migration[7.2]
|
219
|
+
def change
|
220
|
+
add_index :items, "(binary_quantize(embedding)::bit(3)) bit_hamming_ops", using: :hnsw
|
221
|
+
end
|
222
|
+
end
|
223
|
+
```
|
224
|
+
|
225
|
+
### Sparse Vectors
|
226
|
+
|
227
|
+
Use the `sparsevec` type to store sparse vectors
|
228
|
+
|
229
|
+
```ruby
|
230
|
+
class AddEmbeddingToItems < ActiveRecord::Migration[7.2]
|
231
|
+
def change
|
232
|
+
add_column :items, :embedding, :sparsevec, limit: 3 # dimensions
|
233
|
+
end
|
234
|
+
end
|
235
|
+
```
|
236
|
+
|
237
|
+
Get the nearest neighbors
|
238
|
+
|
239
|
+
```ruby
|
240
|
+
embedding = Neighbor::SparseVector.new({0 => 0.9, 1 => 1.3, 2 => 1.1}, 3)
|
241
|
+
Item.nearest_neighbors(:embedding, embedding, distance: "euclidean").first(5)
|
242
|
+
```
|
243
|
+
|
140
244
|
## Examples
|
141
245
|
|
142
246
|
- [OpenAI Embeddings](#openai-embeddings)
|
247
|
+
- [Cohere Embeddings](#cohere-embeddings)
|
248
|
+
- [Sentence Embeddings](#sentence-embeddings)
|
249
|
+
- [Sparse Embeddings](#sparse-embeddings)
|
143
250
|
- [Disco Recommendations](#disco-recommendations)
|
144
251
|
|
145
252
|
### OpenAI Embeddings
|
@@ -170,10 +277,10 @@ def fetch_embeddings(input)
|
|
170
277
|
}
|
171
278
|
data = {
|
172
279
|
input: input,
|
173
|
-
model: "text-embedding-
|
280
|
+
model: "text-embedding-3-small"
|
174
281
|
}
|
175
282
|
|
176
|
-
response = Net::HTTP.post(URI(url), data.to_json, headers)
|
283
|
+
response = Net::HTTP.post(URI(url), data.to_json, headers).tap(&:value)
|
177
284
|
JSON.parse(response.body)["data"].map { |v| v["embedding"] }
|
178
285
|
end
|
179
286
|
```
|
@@ -199,14 +306,221 @@ end
|
|
199
306
|
Document.insert_all!(documents)
|
200
307
|
```
|
201
308
|
|
202
|
-
And get similar
|
309
|
+
And get similar documents
|
310
|
+
|
311
|
+
```ruby
|
312
|
+
document = Document.first
|
313
|
+
document.nearest_neighbors(:embedding, distance: "cosine").first(5).map(&:content)
|
314
|
+
```
|
315
|
+
|
316
|
+
See the [complete code](examples/openai/example.rb)
|
317
|
+
|
318
|
+
### Cohere Embeddings
|
319
|
+
|
320
|
+
Generate a model
|
321
|
+
|
322
|
+
```sh
|
323
|
+
rails generate model Document content:text embedding:bit{1024}
|
324
|
+
rails db:migrate
|
325
|
+
```
|
326
|
+
|
327
|
+
And add `has_neighbors`
|
328
|
+
|
329
|
+
```ruby
|
330
|
+
class Document < ApplicationRecord
|
331
|
+
has_neighbors :embedding
|
332
|
+
end
|
333
|
+
```
|
334
|
+
|
335
|
+
Create a method to call the [embed API](https://docs.cohere.com/reference/embed)
|
336
|
+
|
337
|
+
```ruby
|
338
|
+
def fetch_embeddings(input, input_type)
|
339
|
+
url = "https://api.cohere.com/v1/embed"
|
340
|
+
headers = {
|
341
|
+
"Authorization" => "Bearer #{ENV.fetch("CO_API_KEY")}",
|
342
|
+
"Content-Type" => "application/json"
|
343
|
+
}
|
344
|
+
data = {
|
345
|
+
texts: input,
|
346
|
+
model: "embed-english-v3.0",
|
347
|
+
input_type: input_type,
|
348
|
+
embedding_types: ["ubinary"]
|
349
|
+
}
|
350
|
+
|
351
|
+
response = Net::HTTP.post(URI(url), data.to_json, headers).tap(&:value)
|
352
|
+
JSON.parse(response.body)["embeddings"]["ubinary"].map { |e| e.map { |v| v.chr.unpack1("B*") }.join }
|
353
|
+
end
|
354
|
+
```
|
355
|
+
|
356
|
+
Pass your input
|
357
|
+
|
358
|
+
```ruby
|
359
|
+
input = [
|
360
|
+
"The dog is barking",
|
361
|
+
"The cat is purring",
|
362
|
+
"The bear is growling"
|
363
|
+
]
|
364
|
+
embeddings = fetch_embeddings(input, "search_document")
|
365
|
+
```
|
366
|
+
|
367
|
+
Store the embeddings
|
368
|
+
|
369
|
+
```ruby
|
370
|
+
documents = []
|
371
|
+
input.zip(embeddings) do |content, embedding|
|
372
|
+
documents << {content: content, embedding: embedding}
|
373
|
+
end
|
374
|
+
Document.insert_all!(documents)
|
375
|
+
```
|
376
|
+
|
377
|
+
Embed the search query
|
378
|
+
|
379
|
+
```ruby
|
380
|
+
query = "forest"
|
381
|
+
query_embedding = fetch_embeddings([query], "search_query")[0]
|
382
|
+
```
|
383
|
+
|
384
|
+
And search the documents
|
385
|
+
|
386
|
+
```ruby
|
387
|
+
Document.nearest_neighbors(:embedding, query_embedding, distance: "hamming").first(5).map(&:content)
|
388
|
+
```
|
389
|
+
|
390
|
+
See the [complete code](examples/cohere/example.rb)
|
391
|
+
|
392
|
+
### Sentence Embeddings
|
393
|
+
|
394
|
+
You can generate embeddings locally with [Informers](https://github.com/ankane/informers).
|
395
|
+
|
396
|
+
Generate a model
|
397
|
+
|
398
|
+
```sh
|
399
|
+
rails generate model Document content:text embedding:vector{384}
|
400
|
+
rails db:migrate
|
401
|
+
```
|
402
|
+
|
403
|
+
And add `has_neighbors`
|
404
|
+
|
405
|
+
```ruby
|
406
|
+
class Document < ApplicationRecord
|
407
|
+
has_neighbors :embedding
|
408
|
+
end
|
409
|
+
```
|
410
|
+
|
411
|
+
Load a [model](https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2)
|
412
|
+
|
413
|
+
```ruby
|
414
|
+
model = Informers::Model.new("sentence-transformers/all-MiniLM-L6-v2")
|
415
|
+
```
|
416
|
+
|
417
|
+
Pass your input
|
418
|
+
|
419
|
+
```ruby
|
420
|
+
input = [
|
421
|
+
"The dog is barking",
|
422
|
+
"The cat is purring",
|
423
|
+
"The bear is growling"
|
424
|
+
]
|
425
|
+
embeddings = model.embed(input)
|
426
|
+
```
|
427
|
+
|
428
|
+
Store the embeddings
|
429
|
+
|
430
|
+
```ruby
|
431
|
+
documents = []
|
432
|
+
input.zip(embeddings) do |content, embedding|
|
433
|
+
documents << {content: content, embedding: embedding}
|
434
|
+
end
|
435
|
+
Document.insert_all!(documents)
|
436
|
+
```
|
437
|
+
|
438
|
+
And get similar documents
|
203
439
|
|
204
440
|
```ruby
|
205
441
|
document = Document.first
|
206
442
|
document.nearest_neighbors(:embedding, distance: "cosine").first(5).map(&:content)
|
207
443
|
```
|
208
444
|
|
209
|
-
See the [complete code](examples/
|
445
|
+
See the [complete code](examples/informers/example.rb)
|
446
|
+
|
447
|
+
### Sparse Embeddings
|
448
|
+
|
449
|
+
You can generate sparse embeddings locally with [Transformers.rb](https://github.com/ankane/transformers-ruby).
|
450
|
+
|
451
|
+
Generate a model
|
452
|
+
|
453
|
+
```sh
|
454
|
+
rails generate model Document content:text embedding:sparsevec{30522}
|
455
|
+
rails db:migrate
|
456
|
+
```
|
457
|
+
|
458
|
+
And add `has_neighbors`
|
459
|
+
|
460
|
+
```ruby
|
461
|
+
class Document < ApplicationRecord
|
462
|
+
has_neighbors :embedding
|
463
|
+
end
|
464
|
+
```
|
465
|
+
|
466
|
+
Load a [model](https://huggingface.co/opensearch-project/opensearch-neural-sparse-encoding-v1) to generate embeddings
|
467
|
+
|
468
|
+
```ruby
|
469
|
+
class EmbeddingModel
|
470
|
+
def initialize(model_id)
|
471
|
+
@model = Transformers::AutoModelForMaskedLM.from_pretrained(model_id)
|
472
|
+
@tokenizer = Transformers::AutoTokenizer.from_pretrained(model_id)
|
473
|
+
@special_token_ids = @tokenizer.special_tokens_map.map { |_, token| @tokenizer.vocab[token] }
|
474
|
+
end
|
475
|
+
|
476
|
+
def embed(input)
|
477
|
+
feature = @tokenizer.(input, padding: true, truncation: true, return_tensors: "pt", return_token_type_ids: false)
|
478
|
+
output = @model.(**feature)[0]
|
479
|
+
values = Torch.max(output * feature[:attention_mask].unsqueeze(-1), dim: 1)[0]
|
480
|
+
values = Torch.log(1 + Torch.relu(values))
|
481
|
+
values[0.., @special_token_ids] = 0
|
482
|
+
values.to_a
|
483
|
+
end
|
484
|
+
end
|
485
|
+
|
486
|
+
model = EmbeddingModel.new("opensearch-project/opensearch-neural-sparse-encoding-v1")
|
487
|
+
```
|
488
|
+
|
489
|
+
Pass your input
|
490
|
+
|
491
|
+
```ruby
|
492
|
+
input = [
|
493
|
+
"The dog is barking",
|
494
|
+
"The cat is purring",
|
495
|
+
"The bear is growling"
|
496
|
+
]
|
497
|
+
embeddings = model.embed(input)
|
498
|
+
```
|
499
|
+
|
500
|
+
Store the embeddings
|
501
|
+
|
502
|
+
```ruby
|
503
|
+
documents = []
|
504
|
+
input.zip(embeddings) do |content, embedding|
|
505
|
+
documents << {content: content, embedding: Neighbor::SparseVector.new(embedding)}
|
506
|
+
end
|
507
|
+
Document.insert_all!(documents)
|
508
|
+
```
|
509
|
+
|
510
|
+
Embed the search query
|
511
|
+
|
512
|
+
```ruby
|
513
|
+
query = "forest"
|
514
|
+
query_embedding = model.embed([query])[0]
|
515
|
+
```
|
516
|
+
|
517
|
+
And search the documents
|
518
|
+
|
519
|
+
```ruby
|
520
|
+
Document.nearest_neighbors(:embedding, Neighbor::SparseVector.new(query_embedding), distance: "inner_product").first(5).map(&:content)
|
521
|
+
```
|
522
|
+
|
523
|
+
See the [complete code](examples/sparse/example.rb)
|
210
524
|
|
211
525
|
### Disco Recommendations
|
212
526
|
|
@@ -252,19 +566,7 @@ movie = Movie.find_by(name: "Star Wars (1977)")
|
|
252
566
|
movie.nearest_neighbors(:factors, distance: "cosine").first(5).map(&:name)
|
253
567
|
```
|
254
568
|
|
255
|
-
See the complete code for [cube](examples/
|
256
|
-
|
257
|
-
## Upgrading
|
258
|
-
|
259
|
-
### 0.2.0
|
260
|
-
|
261
|
-
The `distance` option has been moved from `has_neighbors` to `nearest_neighbors`, and there is no longer a default. If you use cosine distance, set:
|
262
|
-
|
263
|
-
```ruby
|
264
|
-
class Item < ApplicationRecord
|
265
|
-
has_neighbors normalize: true
|
266
|
-
end
|
267
|
-
```
|
569
|
+
See the complete code for [cube](examples/disco/item_recs_cube.rb) and [pgvector](examples/disco/item_recs_vector.rb)
|
268
570
|
|
269
571
|
## History
|
270
572
|
|
data/lib/neighbor/model.rb
CHANGED
@@ -49,7 +49,7 @@ module Neighbor
|
|
49
49
|
# TODO move to normalizes when Active Record < 7.1 no longer supported
|
50
50
|
before_save do
|
51
51
|
self.class.neighbor_attributes.each do |k, v|
|
52
|
-
next unless v[:normalize]
|
52
|
+
next unless v[:normalize] && attribute_changed?(k)
|
53
53
|
value = read_attribute(k)
|
54
54
|
next if value.nil?
|
55
55
|
self[k] = Neighbor::Utils.normalize(value, column_info: self.class.columns_hash[k.to_s])
|
@@ -61,6 +61,7 @@ module Neighbor
|
|
61
61
|
scope :nearest_neighbors, ->(attribute_name, vector, options = nil) {
|
62
62
|
raise ArgumentError, "missing keyword: :distance" unless options.is_a?(Hash) && options.key?(:distance)
|
63
63
|
distance = options.delete(:distance)
|
64
|
+
precision = options.delete(:precision)
|
64
65
|
raise ArgumentError, "unknown keywords: #{options.keys.map(&:inspect).join(", ")}" if options.any?
|
65
66
|
|
66
67
|
attribute_name = attribute_name.to_sym
|
@@ -126,6 +127,18 @@ module Neighbor
|
|
126
127
|
vector = Neighbor::Utils.normalize(vector, column_info: column_info) if normalize
|
127
128
|
|
128
129
|
query = connection.quote(column_attribute.serialize(vector))
|
130
|
+
|
131
|
+
if !precision.nil?
|
132
|
+
case precision.to_s
|
133
|
+
when "half"
|
134
|
+
cast_dimensions = dimensions || column_info&.limit
|
135
|
+
raise ArgumentError, "Unknown dimensions" unless cast_dimensions
|
136
|
+
quoted_attribute += "::halfvec(#{connection.quote(cast_dimensions.to_i)})"
|
137
|
+
else
|
138
|
+
raise ArgumentError, "Invalid precision"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
129
142
|
order = "#{quoted_attribute} #{operator} #{query}"
|
130
143
|
if operator == "#"
|
131
144
|
order = "bit_count(#{order})"
|
data/lib/neighbor/railtie.rb
CHANGED
data/lib/neighbor/type/cube.rb
CHANGED
@@ -6,7 +6,8 @@ module Neighbor
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def serialize(value)
|
9
|
-
if
|
9
|
+
if Utils.array?(value)
|
10
|
+
value = value.to_a
|
10
11
|
if value.first.is_a?(Array)
|
11
12
|
value = value.map { |v| serialize_point(v) }.join(", ")
|
12
13
|
else
|
@@ -19,8 +20,8 @@ module Neighbor
|
|
19
20
|
private
|
20
21
|
|
21
22
|
def cast_value(value)
|
22
|
-
if
|
23
|
-
value
|
23
|
+
if Utils.array?(value)
|
24
|
+
value.to_a
|
24
25
|
elsif value.is_a?(Numeric)
|
25
26
|
[value]
|
26
27
|
elsif value.is_a?(String)
|
@@ -6,8 +6,8 @@ module Neighbor
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def serialize(value)
|
9
|
-
if
|
10
|
-
value = "[#{value.map(&:to_f).join(",")}]"
|
9
|
+
if Utils.array?(value)
|
10
|
+
value = "[#{value.to_a.map(&:to_f).join(",")}]"
|
11
11
|
end
|
12
12
|
super(value)
|
13
13
|
end
|
@@ -17,8 +17,8 @@ module Neighbor
|
|
17
17
|
def cast_value(value)
|
18
18
|
if value.is_a?(String)
|
19
19
|
value[1..-1].split(",").map(&:to_f)
|
20
|
-
elsif
|
21
|
-
value
|
20
|
+
elsif Utils.array?(value)
|
21
|
+
value.to_a
|
22
22
|
else
|
23
23
|
raise "can't cast #{value.class.name} to halfvec"
|
24
24
|
end
|
@@ -19,8 +19,8 @@ module Neighbor
|
|
19
19
|
value
|
20
20
|
elsif value.is_a?(String)
|
21
21
|
SparseVector.from_text(value)
|
22
|
-
elsif
|
23
|
-
value = SparseVector.new(value)
|
22
|
+
elsif Utils.array?(value)
|
23
|
+
value = SparseVector.new(value.to_a)
|
24
24
|
else
|
25
25
|
raise "can't cast #{value.class.name} to sparsevec"
|
26
26
|
end
|
data/lib/neighbor/type/vector.rb
CHANGED
@@ -6,8 +6,8 @@ module Neighbor
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def serialize(value)
|
9
|
-
if
|
10
|
-
value = "[#{value.map(&:to_f).join(",")}]"
|
9
|
+
if Utils.array?(value)
|
10
|
+
value = "[#{value.to_a.map(&:to_f).join(",")}]"
|
11
11
|
end
|
12
12
|
super(value)
|
13
13
|
end
|
@@ -17,8 +17,8 @@ module Neighbor
|
|
17
17
|
def cast_value(value)
|
18
18
|
if value.is_a?(String)
|
19
19
|
value[1..-1].split(",").map(&:to_f)
|
20
|
-
elsif
|
21
|
-
value
|
20
|
+
elsif Utils.array?(value)
|
21
|
+
value.to_a
|
22
22
|
else
|
23
23
|
raise "can't cast #{value.class.name} to vector"
|
24
24
|
end
|
data/lib/neighbor/utils.rb
CHANGED
data/lib/neighbor/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: neighbor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-08-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|