Repository: unum-cloud/usearch (language: rust)

Issue:
Feature: Add Int8 support in C# wrapper
### Describe what you are looking for

Add more overloads of `add`/`get`/`search` methods with `Int8` quantization support in C# wrapper

### Can you contribute to the implementation?

- [X] I can contribute

### Is your feature request specific to a certain interface?

Other bindings

### Contact Details

sergey@convermax.com

### Is there an existing issue for this?

- [X] I have searched the existing issues

### Code of Conduct

- [X] I agree to follow this project's Code of Conduct


Discussion / hints:

Fix patch:
diff --git a/csharp/README.md b/csharp/README.md
index 1016a95d..e89241d6 100644
--- a/csharp/README.md
+++ b/csharp/README.md
@@ -14,7 +14,7 @@ using Cloud.Unum.USearch;
 
 using var index = new USearchIndex(
     metricKind: MetricKind.Cos, // Choose cosine metric
-    quantization: ScalarKind.Float32, // Only quantization to Float32, Float64 is currently supported
+    quantization: ScalarKind.Float32, // Only quantization to Float32, Float64, Int8 is currently supported
     dimensions: 3,  // Define the number of dimensions in input vectors
     connectivity: 16, // How frequent should the connections in the graph be, optional
     expansionAdd: 128, // Control the recall of indexing, optional
diff --git a/csharp/src/Cloud.Unum.USearch.Tests/USearchIndexTests.cs b/csharp/src/Cloud.Unum.USearch.Tests/USearchIndexTests.cs
index 508b119f..58587aed 100644
--- a/csharp/src/Cloud.Unum.USearch.Tests/USearchIndexTests.cs
+++ b/csharp/src/Cloud.Unum.USearch.Tests/USearchIndexTests.cs
@@ -165,6 +165,37 @@ public void Add_FloatVector_UpdatesIndexOptions()
         }
     }
 
+    [Fact]
+    public void Add_ByteVector_UpdatesIndexOptions()
+    {
+        // Arrange
+        const uint Dimensions = 10;
+        const uint AddKey = 1;
+        const uint NonExistentKey = 2;
+        const uint ExpectedSize = 1;
+        const uint ExpectedCapacity = 1;
+
+        var indexOptions = new IndexOptions(
+            metricKind: MetricKind.Cos,
+            quantization: ScalarKind.Int8,
+            dimensions: Dimensions
+        );
+
+        var inputVector = GenerateByteVector((int)Dimensions);
+
+        using (var index = new USearchIndex(indexOptions))
+        {
+            // Act
+            index.Add(AddKey, inputVector);
+
+            // Assert
+            Assert.True(index.Contains(AddKey));
+            Assert.False(index.Contains(NonExistentKey));
+            Assert.Equal(ExpectedSize, index.Size());
+            Assert.True(ExpectedCapacity <= index.Capacity());
+        }
+    }
+
     [Fact]
     public void Add_DoubleVector_UpdatesIndexOptions()
     {
@@ -228,6 +259,38 @@ public void Add_ManyFloatVectorsUnderSameKeySeparatelyInMultiKeyIndex_UpdatesInd
         }
     }
 
+    [Fact]
+    public void Add_ManyByteVectorsUnderSameKeySeparatelyInMultiKeyIndex_UpdatesIndexOptions()
+    {
+        // Arrange
+        const uint Dimensions = 10;
+        const ulong AddKey = 1;
+        const int AddFactor = 5;
+
+        var indexOptions = new IndexOptions(
+            metricKind: MetricKind.Cos,
+            quantization: ScalarKind.Int8,
+            dimensions: Dimensions,
+            multi: true
+        );
+
+        var inputVector = GenerateByteVector((int)Dimensions);
+
+        using (var index = new USearchIndex(indexOptions))
+        {
+            // Act
+            for (int i = 0; i < AddFactor; i++)
+            {
+                index.Add(AddKey, inputVector);
+            }
+
+            // Assert
+            Assert.True(index.Contains(AddKey));
+            Assert.Equal((uint)AddFactor, index.Size());
+            Assert.True((uint)AddFactor <= index.Capacity());
+        }
+    }
+
     [Fact]
     public void Add_ManyDoubleVectorsUnderSameKeySeparatelyInMultiKeyIndex_UpdatesIndexOptions()
     {
@@ -293,6 +356,39 @@ public void Add_ManyFloatVectorsUnderSameKeyInBatchInMultiKeyIndex_UpdatesIndexO
         }
     }
 
+    [Fact]
+    public void Add_ManyByteVectorsUnderSameKeyInBatchInMultiKeyIndex_UpdatesIndexOptions()
+    {
+        // Arrange
+        const uint Dimensions = 10;
+        const ulong AddKey = 1;
+        const uint BatchSize = 5;
+
+        var indexOptions = new IndexOptions(
+            metricKind: MetricKind.Cos,
+            quantization: ScalarKind.Int8,
+            dimensions: Dimensions,
+            multi: true
+        );
+
+        (var inputKeys, var inputVectors) = (
+            Enumerable.Repeat(AddKey, (int)BatchSize).ToArray(),
+            GenerateManyByteVectors((int)BatchSize, (int)Dimensions)
+        );
+
+        using (var index = new USearchIndex(indexOptions))
+        {
+            // Act
+            index.Add(inputKeys, inputVectors);
+
+            // Assert
+            Assert.True(index.Contains(AddKey));
+            Assert.Equal(BatchSize, index.Size());
+            Assert.True(BatchSize <= index.Capacity());
+            Assert.Equal((int)BatchSize, index.Count(AddKey));
+        }
+    }
+
     [Fact]
     public void Add_ManyDoubleVectorsUnderSameKeyInBatchInMultiKeyIndex_UpdatesIndexOptions()
     {
@@ -359,6 +455,39 @@ public void Get_ManyFloatVectorsUnderSameKeyInMultiKeyIndex_ReturnsCorrectValue(
         }
     }
 
+    [Fact]
+    public void Get_ManyByteVectorsUnderSameKeyInMultiKeyIndex_ReturnsCorrectValue()
+    {
+        // Arrange
+        const uint Dimensions = 10;
+        const ulong AddKey = 1;
+        const int RetrieveCount = 5;
+        const int BatchSize = 10;
+
+        var indexOptions = new IndexOptions(
+            metricKind: MetricKind.Cos,
+            quantization: ScalarKind.Int8,
+            dimensions: Dimensions,
+            multi: true
+        );
+
+        (var inputKeys, var inputVectors) = (
+            Enumerable.Repeat(AddKey, BatchSize).ToArray(),
+            GenerateManyByteVectors(BatchSize, (int)Dimensions)
+        );
+
+        using (var index = new USearchIndex(indexOptions))
+        {
+            index.Add(inputKeys, inputVectors);
+
+            // Act
+            int foundVectorsCount = index.Get(AddKey, RetrieveCount, out float[][] retrievedVectors);
+
+            // Assert
+            Assert.Equal(RetrieveCount, foundVectorsCount);
+        }
+    }
+
     [Fact]
     public void Get_ManyDoubleVectorsUnderSameKeyInMultiKeyIndex_ReturnsCorrectCountValue()
     {
@@ -419,6 +548,33 @@ public void Get_AfterAddingFloatVector_ReturnsEqualVector()
         }
     }
 
+    [Fact]
+    public void Get_AfterAddingByteVector_ReturnsEqualVector()
+    {
+        // Arrange
+        const uint Dimensions = 10;
+        const ulong AddKey = 1;
+
+        var indexOptions = new IndexOptions(
+            metricKind: MetricKind.Cos,
+            quantization: ScalarKind.Int8,
+            dimensions: Dimensions
+        );
+
+        (var inputKey, var inputVector) = (AddKey, GenerateByteVector((int)Dimensions));
+
+        using (var index = new USearchIndex(indexOptions))
+        {
+            index.Add(inputKey, inputVector);
+
+            // Act
+            index.Get(inputKey, out sbyte[] retrievedVector);
+
+            // Assert
+            Assert.Equal(inputVector, retrievedVector);
+        }
+    }
+
     [Fact]
     public void Get_AfterAddingDoubleVector_ReturnsEqualVector()
     {
@@ -473,6 +629,33 @@ public void Add_AddingTwoFloatVectorsUnderSameKey_ThrowsException()
         }
     }
 
+    [Fact]
+    public void Add_AddingTwoByteVectorsUnderSameKey_ThrowsException()
+    {
+        // Arrange
+        const uint Dimensions = 10;
+        const ulong AddKey = 1;
+
+        var indexOptions = new IndexOptions(
+            metricKind: MetricKind.Cos,
+            quantization: ScalarKind.Int8,
+            dimensions: Dimensions
+        );
+
+        (var inputKey, var inputVector) = (AddKey, GenerateByteVector((int)Dimensions));
+
+        using (var index = new USearchIndex(indexOptions))
+        {
+            index.Add(inputKey, inputVector);
+
+            // Act
+            Action actual = () => index.Add(inputKey, inputVector);
+
+            // Assert
+            Assert.Throws<USearchException>(actual);
+        }
+    }
+
     [Fact]
     public void Add_AddingTwoDoubleVectorsUnderSameKey_ThrowsException()
     {
@@ -529,6 +712,35 @@ public void Remove_InsertedFloatVector_ReturnsInsertedVectorsCount()
         }
     }
 
+    [Fact]
+    public void Remove_InsertedByteVector_ReturnsInsertedVectorsCount()
+    {
+        // Arrange
+        const uint Dimensions = 10;
+        const ulong AddKey = 1;
+        const int ExpectedRemoveReturn = 1;
+
+        var indexOptions = new IndexOptions(
+            metricKind: MetricKind.Cos,
+            quantization: ScalarKind.Int8,
+            dimensions: Dimensions
+        );
+
+        (var inputKey, var inputVector) = (AddKey, GenerateByteVector((int)Dimensions));
+
+
+        using (var index = new USearchIndex(indexOptions))
+        {
+            index.Add(inputKey, inputVector);
+
+            // Act
+            var removedCount = index.Remove(AddKey);
+
+            // Assert
+            Assert.Equal(ExpectedRemoveReturn, removedCount);
+        }
+    }
+
     [Fact]
     public void Remove_InsertedDoubleVector_ReturnsInsertedVectorsCount()
     {
@@ -589,6 +801,38 @@ public void Remove_InsertedManyFloatVectorsUnderSameKeyInMultiKeyIndex_ReturnsIn
         }
     }
 
+    [Fact]
+    public void Remove_InsertedManyByteVectorsUnderSameKeyInMultiKeyIndex_ReturnsInsertedVectorsCount()
+    {
+        // Arrange
+        const uint Dimensions = 10;
+        const ulong AddKey = 1;
+        const int BatchSize = 2;
+
+        var indexOptions = new IndexOptions(
+            metricKind: MetricKind.Cos,
+            quantization: ScalarKind.Int8,
+            dimensions: Dimensions,
+            multi: true
+        );
+
+        (var inputKeys, var inputVectors) = (
+            Enumerable.Repeat(AddKey, BatchSize).ToArray(),
+            GenerateManyByteVectors(BatchSize, (int)Dimensions)
+        );
+
+        using (var index = new USearchIndex(indexOptions))
+        {
+            index.Add(inputKeys, inputVectors);
+
+            // Act
+            var removedCount = index.Remove(AddKey);
+
+            // Assert
+            Assert.Equal(BatchSize, removedCount);
+        }
+    }
+
     [Fact]
     public void Remove_InsertedManyDoubleVectorUnderSameKeyInMultiKeyIndex_ReturnsInsertedVectorsCount()
     {
@@ -964,6 +1208,21 @@ private static float[][] GenerateManyFloatVectors(int n, int vectorLength)
         return result;
     }
 
+    private static sbyte[] GenerateByteVector(int vectorLength)
+    {
+        return Enumerable.Range(0, vectorLength).Select(i => (sbyte)i).ToArray();
+    }
+
+    private static sbyte[][] GenerateManyByteVectors(int n, int vectorLength)
+    {
+        var result = new sbyte[n][];
+        for (int i = 0; i < n; i++)
+        {
+            result[i] = Enumerable.Range(0, vectorLength).Select(i => (sbyte)i).ToArray();
+        }
+        return result;
+    }
+
     private static double[] GenerateDoubleVector(int n)
     {
         return Enumerable.Range(0, n).Select(i => (double)i).ToArray();
diff --git a/csharp/src/Cloud.Unum.USearch/USearchIndex.cs b/csharp/src/Cloud.Unum.USearch/USearchIndex.cs
index 751edb9b..de8a8746 100644
--- a/csharp/src/Cloud.Unum.USearch/USearchIndex.cs
+++ b/csharp/src/Cloud.Unum.USearch/USearchIndex.cs
@@ -187,6 +187,7 @@ private void CheckIncreaseCapacity(ulong size_increase)
             this.IncreaseCapacity(size_increase);
         }
     }
+
     /// <summary>
     /// Adds a vector with a specific key to the index.
     /// </summary>
@@ -208,6 +209,27 @@ public void Add(ulong key, float[] vector)
         }
     }
 
+    /// <summary>
+    /// Adds a vector with a specific key to the index.
+    /// </summary>
+    /// <param name="key">The key associated with the vector.</param>
+    /// <param name="vector">The vector data to be added.</param>
+    public void Add(ulong key, sbyte[] vector)
+    {
+        this.CheckIncreaseCapacity(1);
+        GCHandle handle = GCHandle.Alloc(vector, GCHandleType.Pinned);
+        try
+        {
+            IntPtr vectorPtr = handle.AddrOfPinnedObject();
+            usearch_add(this._index, key, vectorPtr, ScalarKind.Int8, out IntPtr error);
+            HandleError(error);
+        }
+        finally
+        {
+            handle.Free();
+        }
+    }
+
     /// <summary>
     /// Adds a vector with a specific key to the index.
     /// </summary>
@@ -253,6 +275,30 @@ public void Add(ulong[] keys, float[][] vectors)
         }
     }
 
+    /// <summary>
+    /// Adds multiple vectors with specific keys to the index.
+    /// </summary>
+    /// <param name="keys">The keys associated with the vectors.</param>
+    /// <param name="vectors">The vector data to be added.</param>
+    public void Add(ulong[] keys, sbyte[][] vectors)
+    {
+        this.CheckIncreaseCapacity((ulong)vectors.Length);
+        for (int i = 0; i < vectors.Length; i++)
+        {
+            GCHandle handle = GCHandle.Alloc(vectors[i], GCHandleType.Pinned);
+            try
+            {
+                IntPtr vectorPtr = handle.AddrOfPinnedObject();
+                usearch_add(this._index, keys[i], vectorPtr, ScalarKind.Int8, out IntPtr error);
+                HandleError(error);
+            }
+            finally
+            {
+                handle.Free();
+            }
+        }
+    }
+
     /// <summary>
     /// Adds multiple vectors with specific keys to the index.
     /// </summary>
@@ -277,7 +323,6 @@ public void Add(ulong[] keys, double[][] vectors)
         }
     }
 
-
     /// <summary>
     /// Retrieves the vector associated with the given key from the index.
     /// </summary>
@@ -306,6 +351,34 @@ public int Get(ulong key, out float[] vector)
         }
     }
 
+    /// <summary>
+    /// Retrieves the vector associated with the given key from the index.
+    /// </summary>
+    /// <param name="key">The key of the vector to retrieve.</param>
+    /// <param name="vector">The vector data retrieved from the index.</param>
+    /// <returns>The number of vectors found under that key.</returns>
+    public int Get(ulong key, out sbyte[] vector)
+    {
+        vector = new sbyte[this._cachedDimensions];
+        GCHandle handle = GCHandle.Alloc(vector, GCHandleType.Pinned);
+        try
+        {
+            IntPtr vectorPtr = handle.AddrOfPinnedObject();
+            int foundVectorsCount = checked((int)NativeMethods.usearch_get(this._index, key, (UIntPtr)1, vectorPtr, ScalarKind.Int8, out IntPtr error));
+            HandleError(error);
+            if (foundVectorsCount < 1)
+            {
+                vector = null;
+            }
+
+            return foundVectorsCount;
+        }
+        finally
+        {
+            handle.Free();
+        }
+    }
+
     /// <summary>
     /// Retrieves multiple vectors associated with the given key from the index.
     /// </summary>
@@ -344,6 +417,44 @@ public int Get(ulong key, int count, out float[][] vectors)
         }
     }
 
+    /// <summary>
+    /// Retrieves multiple vectors associated with the given key from the index.
+    /// </summary>
+    /// <param name="key">The key of the vectors to retrieve.</param>
+    /// <param name="count">The number of vectors to retrieve.</param>
+    /// <param name="vectors">The vectors data retrieved from the index.</param>
+    /// <returns>The number of vectors found under that key.</returns>
+    public int Get(ulong key, int count, out sbyte[][] vectors)
+    {
+        var flattenVectors = new sbyte[count * (int)this._cachedDimensions];
+        GCHandle handle = GCHandle.Alloc(flattenVectors, GCHandleType.Pinned);
+        try
+        {
+            IntPtr vectorPtr = handle.AddrOfPinnedObject();
+            int foundVectorsCount = checked((int)NativeMethods.usearch_get(this._index, key, (UIntPtr)count, vectorPtr, ScalarKind.Int8, out IntPtr error));
+            HandleError(error);
+            if (foundVectorsCount < 1)
+            {
+                vectors = null;
+            }
+            else
+            {
+                vectors = new sbyte[foundVectorsCount][];
+                for (int i = 0; i < foundVectorsCount; i++)
+                {
+                    vectors[i] = new sbyte[(int)this._cachedDimensions];
+                    Array.Copy(flattenVectors, i * (int)this._cachedDimensions, vectors[i], 0, (int)this._cachedDimensions);
+                }
+            }
+
+            return foundVectorsCount;
+        }
+        finally
+        {
+            handle.Free();
+        }
+    }
+
     /// <summary>
     /// Retrieves the vector associated with the given key from the index.
     /// </summary>
@@ -464,6 +575,19 @@ public int Search(float[] queryVector, int count, out ulong[] keys, out float[]
         return this.Search(queryVector, count, out keys, out distances, ScalarKind.Float32);
     }
 
+    /// <summary>
+    /// Searches for the closest vectors to the query vector.
+    /// </summary>
+    /// <param name="queryVector">The query vector data.</param>
+    /// <param name="count">The number of nearest neighbors to search.</param>
+    /// <param="keys">The keys of the nearest neighbors found.</param>
+    /// <param name="distances">The distances to the nearest neighbors found.</param>
+    /// <returns>The number of matches found.</returns>
+    public int Search(sbyte[] queryVector, int count, out ulong[] keys, out float[] distances)
+    {
+        return this.Search(queryVector, count, out keys, out distances, ScalarKind.Int8);
+    }
+
     /// <summary>
     /// Searches for the closest vectors to the query vector.
     /// </summary>


Test patch: