Skip to content

Commit

Permalink
Expose vector retrieval by key in Swift & Objective-C libraries
Browse files Browse the repository at this point in the history
* enable constructing multi-key indexes in Swift & Objective-C
  • Loading branch information
brittlewis12 committed Aug 3, 2024
1 parent 5ea48c8 commit c814dc4
Show file tree
Hide file tree
Showing 5 changed files with 227 additions and 2 deletions.
53 changes: 51 additions & 2 deletions objc/USearchObjective.mm
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,23 @@ - (UInt32)expansionSearch {
return static_cast<UInt32>(_native->expansion_search());
}

+ (instancetype)make:(USearchMetric)metricKind dimensions:(UInt32)dimensions connectivity:(UInt32)connectivity quantization:(USearchScalar)quantization {
+ (instancetype)make:(USearchMetric)metricKind
dimensions:(UInt32)dimensions
connectivity:(UInt32)connectivity
quantization:(USearchScalar)quantization {
// Create a single-vector index by default
return [self make:metricKind dimensions:dimensions connectivity:connectivity quantization:quantization multi:false];
}

+ (instancetype)make:(USearchMetric)metricKind
dimensions:(UInt32)dimensions
connectivity:(UInt32)connectivity
quantization:(USearchScalar)quantization
multi:(BOOL)multi {
std::size_t dims = static_cast<std::size_t>(dimensions);

index_config_t config(static_cast<std::size_t>(connectivity));
index_dense_config_t config(static_cast<std::size_t>(connectivity));
config.multi = multi;
metric_punned_t metric(dims, to_native_metric(metricKind), to_native_scalar(quantization));
if (metric.missing()) {
@throw [NSException exceptionWithName:@"Can't create an index"
Expand Down Expand Up @@ -158,6 +171,18 @@ - (UInt32)searchSingle:(Float32 const *_Nonnull)vector
return static_cast<UInt32>(found);
}

- (UInt32)getSingle:(USearchKey)key
vector:(void *_Nonnull)vector
count:(UInt32)wanted {
std::size_t result = _native->get(key, (f32_t*)vector, static_cast<std::size_t>(wanted));

if (!result) {
return 0;
}

return static_cast<UInt32>(result);
}

- (void)addDouble:(USearchKey)key
vector:(Float64 const *_Nonnull)vector {
add_result_t result = _native->add(key, (f64_t const *)vector);
Expand Down Expand Up @@ -185,6 +210,18 @@ - (UInt32)searchDouble:(Float64 const *_Nonnull)vector
return static_cast<UInt32>(found);
}

- (UInt32)getDouble:(USearchKey)key
vector:(void *_Nonnull)vector
count:(UInt32)wanted {
std::size_t result = _native->get(key, (f64_t*)vector, static_cast<std::size_t>(wanted));

if (!result) {
return 0;
}

return static_cast<UInt32>(result);
}

- (void)addHalf:(USearchKey)key
vector:(void const *_Nonnull)vector {
add_result_t result = _native->add(key, (f16_t const *)vector);
Expand Down Expand Up @@ -212,6 +249,18 @@ - (UInt32)searchHalf:(void const *_Nonnull)vector
return static_cast<UInt32>(found);
}

- (UInt32)getHalf:(USearchKey)key
vector:(void *_Nonnull)vector
count:(UInt32)wanted {
std::size_t result = _native->get(key, (f16_t*)vector, static_cast<std::size_t>(wanted));

if (!result) {
return 0;
}

return static_cast<UInt32>(result);
}

- (void)clear {
_native->clear();
}
Expand Down
48 changes: 48 additions & 0 deletions objc/include/USearchObjective.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,22 @@ API_AVAILABLE(ios(13.0), macos(10.15), tvos(13.0), watchos(6.0))
*/
+ (instancetype)make:(USearchMetric)metric dimensions:(UInt32)dimensions connectivity:(UInt32)connectivity quantization:(USearchScalar)quantization NS_SWIFT_NAME(make(metric:dimensions:connectivity:quantization:));

/**
* @brief Initializes a new index.
* @param metric The distance function to compare the dis-similarity of vectors.
* @param dimensions The number of dimensions planned for this index.
* @param connectivity Number of connections per node in the proximity graph.
* Higher connectivity improves quantization, increases memory usage, and reduces construction speed.
* @param quantization Quantization of internal vector representations. Lower quantization means higher speed.
* @param multi Enables indexing multiple vectors per key when true.
*/
+ (instancetype)make:(USearchMetric)metricKind
dimensions:(UInt32)dimensions
connectivity:(UInt32)connectivity
quantization:(USearchScalar)quantization
multi:(BOOL)multi NS_SWIFT_NAME(make(metric:dimensions:connectivity:quantization:multi:));


/**
* @brief Pre-allocates space in the index for the given number of vectors.
*/
Expand All @@ -77,6 +93,17 @@ API_AVAILABLE(ios(13.0), macos(10.15), tvos(13.0), watchos(6.0))
keys:(USearchKey *_Nullable)keys
distances:(Float32 *_Nullable)distances NS_SWIFT_NAME(searchSingle(vector:count:keys:distances:));

/**
* @brief Retrieves a labeled single-precision vector from the index.
* @param vector A buffer to store the vector.
* @param count For multi-indexes, the number of vectors to retrieve.
* @param error Optional output buffer for error messages.
* @return Number of vectors exported to `vector`.
*/
- (UInt32)getSingle:(USearchKey)key
vector:(void *_Nonnull)vector
count:(UInt32)count NS_SWIFT_NAME(getSingle(key:vector:count:));

/**
* @brief Adds a labeled vector to the index.
* @param vector Double-precision vector.
Expand All @@ -97,6 +124,17 @@ API_AVAILABLE(ios(13.0), macos(10.15), tvos(13.0), watchos(6.0))
keys:(USearchKey *_Nullable)keys
distances:(Float32 *_Nullable)distances NS_SWIFT_NAME(searchDouble(vector:count:keys:distances:));

/**
* @brief Retrieves a labeled double-precision vector from the index.
* @param vector A buffer to store the vector.
* @param count For multi-indexes, the number of vectors to retrieve.
* @param error Optional output buffer for error messages.
* @return Number of vectors exported to `vector`.
*/
- (UInt32)getDouble:(USearchKey)key
vector:(void *_Nonnull)vector
count:(UInt32)count NS_SWIFT_NAME(getDouble(key:vector:count:));

/**
* @brief Adds a labeled vector to the index.
* @param vector Half-precision vector.
Expand All @@ -117,6 +155,16 @@ API_AVAILABLE(ios(13.0), macos(10.15), tvos(13.0), watchos(6.0))
keys:(USearchKey *_Nullable)keys
distances:(Float32 *_Nullable)distances NS_SWIFT_NAME(searchHalf(vector:count:keys:distances:));

/**
* @brief Retrieves a labeled half-precision vector from the index.
* @param vector A buffer to store the vector.
* @param count For multi-indexes, the number of vectors to retrieve.
* @param error Optional output buffer for error messages.
* @return Number of vectors exported to `vector`.
*/
- (UInt32)getHalf:(USearchKey)key
vector:(void *_Nonnull)vector
count:(UInt32)count NS_SWIFT_NAME(getHalf(key:vector:count:));

- (Boolean)contains:(USearchKey)key NS_SWIFT_NAME(contains(key:));

Expand Down
76 changes: 76 additions & 0 deletions swift/Index+Sugar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,31 @@ extension USearchIndex {
return search(vector: vector[...], count: count)
}

/// Retrieve vectors for a given key.
/// - Parameter key: Unique identifier for that object.
/// - Parameter count: For multi-indexes, Number of vectors to retrieve. Defaults to 1.
/// - Returns: Two-dimensional array of Single-precision vectors.
/// - Throws: If runs out of memory.
public func get(key: USearchKey, count: Int = 1) -> [[Float]]? {
var vector: [Float] = Array(repeating: 0.0, count: Int(self.dimensions) * count)
let returnedCount = vector.withContiguousMutableStorageIfAvailable { buf in
guard let baseAddress = buf.baseAddress else { return UInt32(0) }
return getSingle(
key: key,
vector: baseAddress,
count: CUnsignedInt(count)
)
}
guard let count = returnedCount, count > 0 else { return nil }
return stride(
from: 0,
to: Int(count) * Int(self.dimensions),
by: Int(self.dimensions)
).map {
Array(vector[$0 ..< $0 + Int(self.dimensions)])
}
}

/// Adds a labeled vector to the index.
/// - Parameter key: Unique identifier for that object.
/// - Parameter vector: Double-precision vector.
Expand Down Expand Up @@ -97,6 +122,31 @@ extension USearchIndex {
search(vector: vector[...], count: count)
}

/// Retrieve vectors for a given key.
/// - Parameter key: Unique identifier for that object.
/// - Parameter count: For multi-indexes, Number of vectors to retrieve. Defaults to 1.
/// - Returns: Two-dimensional array of Double-precision vectors.
/// - Throws: If runs out of memory.
public func get(key: USearchKey, count: Int = 1) -> [[Float64]]? {
var vector: [Float64] = Array(repeating: 0.0, count: Int(self.dimensions) * count)
let count = vector.withContiguousMutableStorageIfAvailable { buf in
guard let baseAddress = buf.baseAddress else { return UInt32(0) }
return getDouble(
key: key,
vector: baseAddress,
count: CUnsignedInt(count)
)
}
guard let count = count, count > 0 else { return nil }
return stride(
from: 0,
to: Int(count) * Int(self.dimensions),
by: Int(self.dimensions)
).map {
Array(vector[$0 ..< $0 + Int(self.dimensions)])
}
}

#if arch(arm64)

/// Adds a labeled vector to the index.
Expand Down Expand Up @@ -146,6 +196,32 @@ extension USearchIndex {
search(vector: vector[...], count: count)
}

/// Retrieve vectors for a given key.
/// - Parameter key: Unique identifier for that object.
/// - Parameter count: For multi-indexes, Number of vectors to retrieve. Defaults to 1.
/// - Returns: Two-dimensional array of Half-precision vectors.
/// - Throws: If runs out of memory.
@available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)
public func get(key: USearchKey, count: Int = 1) -> [[Float16]]? {
var vector: [Float16] = Array(repeating: 0.0, count: Int(self.dimensions) * count)
let count = vector.withContiguousMutableStorageIfAvailable { buf in
guard let baseAddress = buf.baseAddress else { return UInt32(0) }
return getSingle(
key: key,
vector: baseAddress,
count: CUnsignedInt(count)
)
}
guard let count = count, count > 0 else { return nil }
return stride(
from: 0,
to: Int(count) * Int(self.dimensions),
by: Int(self.dimensions)
).map {
Array(vector[$0 ..< $0 + Int(self.dimensions)])
}
}

#endif

/// Number of vectors in the index.
Expand Down
2 changes: 2 additions & 0 deletions swift/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ index.add(key: 43, vector: vectorB)

let results = index.search(vector: vectorA, count: 10)
assert(results.0[0] == 42)
let retrieved: [[Float32]]? = index.get(key: 42)
assert(retrieved![0] == vectorA)
```

If using in a SwiftUI application, make sure to annulate the void responses:
Expand Down
50 changes: 50 additions & 0 deletions swift/Test.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,61 @@ class Test: XCTestCase {
let results = index.search(vector: vectorA, count: 10)
assert(results.0[0] == 42)

let fetched: [[Float]]? = index.get(key: 42)
assert(fetched?[0] == vectorA)

assert(index.contains(key: 42))
assert(index.count(key: 42) == 1)
assert(index.count(key: 49) == 0)
index.rename(from: 42, to: 49)
assert(index.count(key: 49) == 1)

let refetched: [[Float]]? = index.get(key: 49)
assert(refetched?[0] == vectorA)
let stale: [[Float]]? = index.get(key: 42)
assert(stale == nil)

index.remove(key: 49)
assert(index.count(key: 49) == 0)
}

func testUnitMulti() throws {
let index = USearchIndex.make(
metric: USearchMetric.l2sq,
dimensions: 4,
connectivity: 8,
quantization: USearchScalar.F32,
multi: true
)
let vectorA: [Float32] = [0.3, 0.5, 1.2, 1.4]
let vectorB: [Float32] = [0.4, 0.2, 1.2, 1.1]
index.reserve(2)

// Adding a slice
index.add(key: 42, vector: vectorA[...])

// Adding a vector
index.add(key: 42, vector: vectorB)

let results = index.search(vector: vectorA, count: 10)
assert(results.0[0] == 42)

let fetched: [[Float]]? = index.get(key: 42, count: 2)
assert(fetched?.contains(vectorA) == true)
assert(fetched?.contains(vectorB) == true)

assert(index.contains(key: 42))
assert(index.count(key: 42) == 2)
assert(index.count(key: 49) == 0)
index.rename(from: 42, to: 49)
assert(index.count(key: 49) == 2)

let refetched: [[Float]]? = index.get(key: 49, count: 2)
assert(refetched?.contains(vectorA) == true)
assert(refetched?.contains(vectorB) == true)
let stale: [[Float]]? = index.get(key: 42)
assert(stale == nil)

index.remove(key: 49)
assert(index.count(key: 49) == 0)
}
Expand Down

0 comments on commit c814dc4

Please sign in to comment.