Class: Neighbor::Vector

Inherits:
ActiveRecord::Type::Value
  • Object
show all
Defined in:
lib/neighbor/vector.rb

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(dimensions:, normalize:, model:, attribute_name:) ⇒ Vector

Returns a new instance of Vector.



3
4
5
6
7
8
9
# File 'lib/neighbor/vector.rb', line 3

def initialize(dimensions:, normalize:, model:, attribute_name:)
  super()
  @dimensions = dimensions
  @normalize = normalize
  @model = model
  @attribute_name = attribute_name
end

Class Method Details

.cast(value, dimensions:, normalize:, column_info:) ⇒ Object

Raises:



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/neighbor/vector.rb', line 11

def self.cast(value, dimensions:, normalize:, column_info:)
  value = value.to_a.map(&:to_f)

  dimensions ||= column_info[:dimensions]
  raise Error, "Expected #{dimensions} dimensions, not #{value.size}" if dimensions && value.size != dimensions

  raise Error, "Values must be finite" unless value.all?(&:finite?)

  if normalize
    norm = Math.sqrt(value.sum { |v| v * v })

    # store zero vector as all zeros
    # since NaN makes the distance always 0
    # could also throw error

    # safe to update in-place since earlier map dups
    value.map! { |v| v / norm } if norm > 0
  end

  value
end

.column_info(model, attribute_name) ⇒ Object



33
34
35
36
37
38
39
40
# File 'lib/neighbor/vector.rb', line 33

def self.column_info(model, attribute_name)
  attribute_name = attribute_name.to_s
  column = model.columns.detect { |c| c.name == attribute_name }
  {
    type: column.try(:type),
    dimensions: column.try(:limit)
  }
end

Instance Method Details

#cast(value) ⇒ Object



47
48
49
# File 'lib/neighbor/vector.rb', line 47

def cast(value)
  self.class.cast(value, dimensions: @dimensions, normalize: @normalize, column_info: column_info) unless value.nil?
end

#column_infoObject

need to be careful to avoid loading column info before needed



43
44
45
# File 'lib/neighbor/vector.rb', line 43

def column_info
  @column_info ||= self.class.column_info(@model, @attribute_name)
end

#deserialize(value) ⇒ Object



61
62
63
# File 'lib/neighbor/vector.rb', line 61

def deserialize(value)
  value[1..-1].split(",").map(&:to_f) unless value.nil?
end

#serialize(value) ⇒ Object



51
52
53
54
55
56
57
58
59
# File 'lib/neighbor/vector.rb', line 51

def serialize(value)
  unless value.nil?
    if column_info[:type] == :vector
      "[#{cast(value).join(", ")}]"
    else
      "(#{cast(value).join(", ")})"
    end
  end
end