lib/ngt/index.rb in ngt-0.2.4 vs lib/ngt/index.rb in ngt-0.3.0

- old
+ new

@@ -1,37 +1,47 @@ module Ngt class Index include Utils - DISTANCE_TYPES = [:l1, :l2, :hamming, :angle, :cosine, :normalized_angle, :normalized_cosine, :jaccard] + attr_reader :path - attr_reader :dimensions, :distance_type, :edge_size_for_creation, :edge_size_for_search, :object_type, :path - - def initialize(path) + def initialize(index, path) + @index = index @path = path + @error = FFI.ngt_create_error_object - @index = ffi(:ngt_open_index, path) + @property = ffi(:ngt_create_property) + ffi(:ngt_get_property, @index, @property) - property = ffi(:ngt_create_property) - ffi(:ngt_get_property, @index, property) + ObjectSpace.define_finalizer(self, self.class.finalize(@error, @index, @property)) + end - @dimensions = ffi(:ngt_get_property_dimension, property) - @distance_type = DISTANCE_TYPES[ffi(:ngt_get_property_distance_type, property)] - @edge_size_for_creation = ffi(:ngt_get_property_edge_size_for_creation, property) - @edge_size_for_search = ffi(:ngt_get_property_edge_size_for_search, property) + def dimensions + @dimensions ||= ffi(:ngt_get_property_dimension, @property) + end - object_type = ffi(:ngt_get_property_object_type, property) - @float = FFI.ngt_is_property_object_type_float(object_type) - @object_type = @float ? :float : :integer + def distance_type + @distance_type ||= ffi(:ngt_get_property_distance_type, @property) + end - @object_space = ffi(:ngt_get_object_space, @index) + def edge_size_for_creation + @edge_size_for_creation ||= ffi(:ngt_get_property_edge_size_for_creation, @property) + end - ObjectSpace.define_finalizer(self, self.class.finalize(@error)) + def edge_size_for_search + @edge_size_for_search ||= ffi(:ngt_get_property_edge_size_for_search, @property) end + def object_type + @object_type ||= begin + object_type = ffi(:ngt_get_property_object_type, @property) + FFI.ngt_is_property_object_type_float(object_type) ? :float : :integer + end + end + def insert(object) - ffi(:ngt_insert_index, @index, c_object(object.to_a), @dimensions) + ffi(:ngt_insert_index, @index, c_object(object.to_a), dimensions) end def batch_insert(objects, num_threads: 8) if narray?(objects) objects = objects.cast_to(Numo::SFloat) unless objects.is_a?(Numo::SFloat) @@ -57,104 +67,113 @@ def build_index(num_threads: 8) ffi(:ngt_create_index, @index, num_threads) end def object(id) - if float? + if object_type == :float res = ffi(:ngt_get_object_as_float, @object_space, id) - res.read_array_of_float(@dimensions) + res.read_array_of_float(dimensions) else res = ffi(:ngt_get_object_as_integer, @object_space, id) - res.read_array_of_uint8(@dimensions) + res.read_array_of_uint8(dimensions) end end def remove(id) ffi(:ngt_remove_index, @index, id) end def search(query, size: 20, epsilon: 0.1, radius: nil) radius ||= -1.0 results = ffi(:ngt_create_empty_results) - ffi(:ngt_search_index, @index, c_object(query.to_a), @dimensions, size, epsilon, radius, results) + ffi(:ngt_search_index, @index, c_object(query.to_a), dimensions, size, epsilon, radius, results) result_size = ffi(:ngt_get_result_size, results) ret = [] result_size.times do |i| res = ffi(:ngt_get_result, results, i) ret << { id: res[:id], distance: res[:distance] } end ret + ensure + FFI.ngt_destroy_results(results) if results end def save(path2 = nil, path: nil) warn "[ngt] Passing path as an option is deprecated - use an argument instead" if path - path ||= path2 || @path - ffi(:ngt_save_index, @index, path) + @path = path || path2 || @path || Dir.mktmpdir + ffi(:ngt_save_index, @index, @path) end def close FFI.ngt_close_index(@index) end def self.new(dimensions, path: nil, edge_size_for_creation: 10, edge_size_for_search: 40, object_type: :float, distance_type: :l2) - # called from load - return super(path) if path && dimensions.nil? + error = FFI.ngt_create_error_object - # TODO remove in 0.3.0 - create = dimensions.is_a?(Integer) || path - unless create + # TODO remove in 0.4.0 + if !dimensions.is_a?(Integer) && !path warn "[ngt] Passing a path to new is deprecated - use load instead" - return super(dimensions) + path = dimensions + dimensions = nil end - path ||= Dir.mktmpdir - error = FFI.ngt_create_error_object - property = ffi(:ngt_create_property, error) - ffi(:ngt_set_property_dimension, property, dimensions, error) - ffi(:ngt_set_property_edge_size_for_creation, property, edge_size_for_creation, error) - ffi(:ngt_set_property_edge_size_for_search, property, edge_size_for_search, error) - - case object_type.to_s.downcase - when "float" - ffi(:ngt_set_property_object_type_float, property, error) - when "integer" - ffi(:ngt_set_property_object_type_integer, property, error) + if path && dimensions.nil? + index = ffi(:ngt_open_index, path, error) else - raise ArgumentError, "Unknown object type: #{object_type}" - end + property = ffi(:ngt_create_property, error) + ffi(:ngt_set_property_dimension, property, dimensions, error) + ffi(:ngt_set_property_edge_size_for_creation, property, edge_size_for_creation, error) + ffi(:ngt_set_property_edge_size_for_search, property, edge_size_for_search, error) - case distance_type.to_s.downcase - when "l1" - ffi(:ngt_set_property_distance_type_l1, property, error) - when "l2" - ffi(:ngt_set_property_distance_type_l2, property, error) - when "angle" - ffi(:ngt_set_property_distance_type_angle, property, error) - when "hamming" - ffi(:ngt_set_property_distance_type_hamming, property, error) - when "jaccard" - ffi(:ngt_set_property_distance_type_jaccard, property, error) - when "cosine" - ffi(:ngt_set_property_distance_type_cosine, property, error) - else - raise ArgumentError, "Unknown distance type: #{distance_type}" - end + case object_type.to_s.downcase + when "float" + ffi(:ngt_set_property_object_type_float, property, error) + when "integer" + ffi(:ngt_set_property_object_type_integer, property, error) + else + raise ArgumentError, "Unknown object type: #{object_type}" + end - index = ffi(:ngt_create_graph_and_tree, path, property, error) - FFI.ngt_close_index(index) - index = nil + case distance_type.to_s.downcase + when "l1" + ffi(:ngt_set_property_distance_type_l1, property, error) + when "l2" + ffi(:ngt_set_property_distance_type_l2, property, error) + when "angle" + ffi(:ngt_set_property_distance_type_angle, property, error) + when "hamming" + ffi(:ngt_set_property_distance_type_hamming, property, error) + when "jaccard" + ffi(:ngt_set_property_distance_type_jaccard, property, error) + when "cosine" + ffi(:ngt_set_property_distance_type_cosine, property, error) + when "normalized_angle" + ffi(:ngt_set_property_distance_type_normalized_angle, property, error) + when "normalized_cosine" + ffi(:ngt_set_property_distance_type_normalized_cosine, property, error) + else + raise ArgumentError, "Unknown distance type: #{distance_type}" + end - super(path) + index = + if path + ffi(:ngt_create_graph_and_tree, path, property, error) + else + ffi(:ngt_create_graph_and_tree_in_memory, property, error) + end + end + + super(index, path) ensure FFI.ngt_destroy_error_object(error) if error FFI.ngt_destroy_property(property) if property - FFI.ngt_close_index(index) if index end def self.load(path) new(nil, path: path) end @@ -167,25 +186,22 @@ # private def self.ffi(*args) Utils.ffi(*args) end - def self.finalize(error) + def self.finalize(error, index, property) # must use proc instead of stabby lambda proc do - # TODO clean-up more objects FFI.ngt_destroy_error_object(error) + FFI.ngt_close_index(index) + FFI.ngt_destroy_property(property) end end private def narray?(data) defined?(Numo::NArray) && data.is_a?(Numo::NArray) - end - - def float? - @float end def c_object(object) c_object = ::FFI::MemoryPointer.new(:double, object.size) c_object.write_array_of_double(object)