usearch 1.2.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/prerelease.yml +71 -0
- package/.github/workflows/release.yml +77 -2
- package/.github/workflows/update_version.sh +1 -0
- package/.gitignore +11 -1
- package/.vscode/settings.json +5 -1
- package/CMakeLists.txt +1 -0
- package/Cargo.toml +1 -1
- package/README.md +31 -18
- package/c/lib.cpp +38 -4
- package/c/test.c +117 -114
- package/c/usearch.h +28 -1
- package/conanfile.py +1 -1
- package/cpp/test.cpp +2 -2
- package/csharp/.editorconfig +361 -0
- package/csharp/Cloud.Unum.USearch.sln +34 -0
- package/csharp/DEV_SETUP.md +51 -0
- package/csharp/Directory.Build.props +29 -0
- package/csharp/README.md +77 -0
- package/csharp/build_and_test.cmd +31 -0
- package/csharp/build_and_test.sh +30 -0
- package/csharp/eng/build_lib.sh +65 -0
- package/csharp/eng/clean.sh +12 -0
- package/csharp/nuget/NUGET.md +11 -0
- package/csharp/nuget/nuget-package.props +33 -0
- package/csharp/pack.sh +56 -0
- package/csharp/src/Cloud.Unum.USearch.Tests/Cloud.Unum.USearch.Tests.csproj +39 -0
- package/csharp/src/Cloud.Unum.USearch.Tests/InitTests.cs +306 -0
- package/csharp/src/Cloud.Unum.USearch.Tests/Usings.cs +1 -0
- package/docs/benchmarks.md +6 -6
- package/docs/compilation.md +1 -1
- package/include/usearch/index.hpp +172 -52
- package/include/usearch/index_dense.hpp +201 -64
- package/include/usearch/index_plugins.hpp +90 -69
- package/package.json +2 -2
- package/python/lib.cpp +99 -37
- package/python/scripts/test_index.py +38 -10
- package/python/usearch/index.py +87 -37
- package/rust/README.md +2 -2
- package/rust/lib.cpp +42 -19
- package/rust/lib.hpp +14 -0
- package/rust/lib.rs +85 -43
- package/wasmer.toml +1 -1
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Get absolute path to the usearch dir
|
|
4
|
+
# FIX: realpath is not available on OSX, so use pwd
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
6
|
+
SCRIPT_DIR="${SCRIPT_DIR}/"
|
|
7
|
+
|
|
8
|
+
# Define project paths
|
|
9
|
+
REPO_ROOT=`cd -P "${SCRIPT_DIR}../.." && pwd`
|
|
10
|
+
REPO_ROOT="${REPO_ROOT}/"
|
|
11
|
+
C_DIR="${REPO_ROOT}c/"
|
|
12
|
+
CSHARP_DIR="${REPO_ROOT}csharp/"
|
|
13
|
+
BUILD_ARTIFACTS_DIR="${CSHARP_DIR}lib/"
|
|
14
|
+
|
|
15
|
+
LIB_FILE="libusearch_c"
|
|
16
|
+
|
|
17
|
+
# Determine the architecture type (only x64 is supported)
|
|
18
|
+
ARCH_TYPE="$(uname -m)"
|
|
19
|
+
case "$ARCH_TYPE" in
|
|
20
|
+
x86_64)
|
|
21
|
+
ARCH="x64"
|
|
22
|
+
;;
|
|
23
|
+
# i386|i486|i586|i686)
|
|
24
|
+
# ARCH="x86"
|
|
25
|
+
# ;;
|
|
26
|
+
*)
|
|
27
|
+
echo "Unsupported architecture"
|
|
28
|
+
exit 1
|
|
29
|
+
;;
|
|
30
|
+
esac
|
|
31
|
+
echo "Detected architecture: $ARCH"
|
|
32
|
+
|
|
33
|
+
# Determine the OS type (linux or macos) and set the shared library extension accordingly
|
|
34
|
+
case "$(uname -s)" in
|
|
35
|
+
Darwin)
|
|
36
|
+
BUILD_ARTIFACTS_DIR+="runtimes/osx-${ARCH}/native/"
|
|
37
|
+
LIB_FILE+=".dylib"
|
|
38
|
+
;;
|
|
39
|
+
Linux)
|
|
40
|
+
BUILD_ARTIFACTS_DIR+="runtimes/linux-${ARCH}/native/"
|
|
41
|
+
LIB_FILE+=".so"
|
|
42
|
+
;;
|
|
43
|
+
*)
|
|
44
|
+
echo "Unsupported operating system"
|
|
45
|
+
exit 1
|
|
46
|
+
;;
|
|
47
|
+
esac
|
|
48
|
+
|
|
49
|
+
MAKE_TARGET="${LIB_FILE}"
|
|
50
|
+
|
|
51
|
+
# Build the native dynamic lib
|
|
52
|
+
cd "$C_DIR" || { echo "C directory not found in $C_DIR"; exit 1; }
|
|
53
|
+
if [ ! -f Makefile ]; then
|
|
54
|
+
echo "Makefile not found in $C_DIR"
|
|
55
|
+
exit 1
|
|
56
|
+
fi
|
|
57
|
+
make "${MAKE_TARGET}"
|
|
58
|
+
if [ $? -ne 0 ]; then
|
|
59
|
+
echo "Build failed!"
|
|
60
|
+
exit 1
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
# Create output dir for shared lib and copy it there
|
|
64
|
+
mkdir -p "$BUILD_ARTIFACTS_DIR"
|
|
65
|
+
cp "${C_DIR}/${LIB_FILE}" "$BUILD_ARTIFACTS_DIR"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Get absolute path to the usearch csharp dir
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
5
|
+
SCRIPT_DIR="${SCRIPT_DIR}/"
|
|
6
|
+
CSHARP_DIR=`cd -P "${SCRIPT_DIR}.." && pwd`
|
|
7
|
+
CSHARP_DIR="${CSHARP_DIR}/"
|
|
8
|
+
|
|
9
|
+
rm -rf ${CSHARP_DIR}lib # Local temp path for native libs
|
|
10
|
+
rm -rf ${CSHARP_DIR}packages # Local temp path for nuget packages
|
|
11
|
+
find ${CSHARP_DIR} -type d -name 'bin' -exec rm -rf {} +
|
|
12
|
+
find ${CSHARP_DIR} -type d -name 'obj' -exec rm -rf {} +
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
**Cloud.Unum.USearch** is a wrapper for the USearch vector search engine, offering fast searches, multiple metrics, and features like SIMD-optimized metrics.
|
|
2
|
+
|
|
3
|
+
**Supported metrics**: Euclidean, Angular, Jaccard, Hamming, Haversine, User-Defined Metrics
|
|
4
|
+
|
|
5
|
+
**Platforms**: Linux, MacOS, Windows
|
|
6
|
+
|
|
7
|
+
# Getting Started ⚡
|
|
8
|
+
|
|
9
|
+
- [Quickstart](https://github.com/unum-cloud/usearch/blob/main/csharp/README.md)
|
|
10
|
+
- Join the [Discord community](https://discord.gg/A6wxt6dS9j)
|
|
11
|
+
- Check out the [GitHub repository](https://github.com/unum-cloud/usearch)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<Project>
|
|
3
|
+
<PropertyGroup>
|
|
4
|
+
<Version Condition="'$(Version)' == ''">0.0.2</Version>
|
|
5
|
+
|
|
6
|
+
<Authors>Unum</Authors>
|
|
7
|
+
<Company>Unum</Company>
|
|
8
|
+
<PackageTags>USearch Vector Search Disk-based Multi-Modal Semantic-Search Molecular-Search</PackageTags>
|
|
9
|
+
<PackageId>Cloud.Unum.USearch</PackageId>
|
|
10
|
+
|
|
11
|
+
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
|
12
|
+
<PackageProjectUrl>https://github.com/unum-cloud/usearch</PackageProjectUrl>
|
|
13
|
+
<RepositoryUrl>https://github.com/unum-cloud/usearch</RepositoryUrl>
|
|
14
|
+
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
|
15
|
+
|
|
16
|
+
<PackageIcon>unum.png</PackageIcon>
|
|
17
|
+
<PackageIconUrl>unum.png</PackageIconUrl>
|
|
18
|
+
<PackageReadmeFile>NUGET.md</PackageReadmeFile>
|
|
19
|
+
</PropertyGroup>
|
|
20
|
+
|
|
21
|
+
<ItemGroup>
|
|
22
|
+
<None Include="$(NativeLibDir)runtimes\**\*.*" Pack="true" PackagePath="runtimes" />
|
|
23
|
+
<None Include="$(RepoRoot)LICENSE" Pack="true" PackagePath=""/>
|
|
24
|
+
<None Include="$(RepoRoot)assets\unum.png" Pack="true" PackagePath=""/>
|
|
25
|
+
<None Include="$(CSharpDir)nuget\NUGET.md" Pack="true" PackagePath=""/>
|
|
26
|
+
</ItemGroup>
|
|
27
|
+
|
|
28
|
+
<!-- Package validation https://learn.microsoft.com/en-us/dotnet/fundamentals/package-validation/overview?WT.mc_id=DT-MVP-5003978 -->
|
|
29
|
+
<PropertyGroup Condition="'$(IsPackable)' == 'true'">
|
|
30
|
+
<EnablePackageValidation Condition="'$(EnablePackageValidation)' == ''">true</EnablePackageValidation>
|
|
31
|
+
</PropertyGroup>
|
|
32
|
+
|
|
33
|
+
</Project>
|
package/csharp/pack.sh
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
check_task_status() {
|
|
4
|
+
local task_name=$1
|
|
5
|
+
local exit_code=$2
|
|
6
|
+
|
|
7
|
+
if [ $exit_code -ne 0 ]; then
|
|
8
|
+
echo "$task_name failed!"
|
|
9
|
+
exit 1
|
|
10
|
+
else
|
|
11
|
+
echo "$task_name finished successfully!"
|
|
12
|
+
fi
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
# Check vars for pack and push are set
|
|
16
|
+
required_vars=("PACKAGE_ID" "PACKAGE_VERSION" "NUGET_APIKEY" "NUGET_SERVER")
|
|
17
|
+
unset_vars=""
|
|
18
|
+
|
|
19
|
+
for var in "${required_vars[@]}"; do
|
|
20
|
+
if [ -z "${!var}" ]; then
|
|
21
|
+
unset_vars="$unset_vars $var"
|
|
22
|
+
fi
|
|
23
|
+
done
|
|
24
|
+
|
|
25
|
+
if [ -n "$unset_vars" ]; then
|
|
26
|
+
echo "The following variables are not set:$unset_vars"
|
|
27
|
+
exit 1
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# Get absolute path to the usearch dir
|
|
31
|
+
# FIX: realpath is not available on OSX, so use pwd
|
|
32
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
33
|
+
SCRIPT_DIR="${SCRIPT_DIR}/"
|
|
34
|
+
|
|
35
|
+
# Define project paths
|
|
36
|
+
REPO_ROOT=`cd -P "$SCRIPT_DIR../" && pwd`
|
|
37
|
+
REPO_ROOT="${REPO_ROOT}/"
|
|
38
|
+
|
|
39
|
+
# Clear package output folder
|
|
40
|
+
rm -rf ./packages/*
|
|
41
|
+
|
|
42
|
+
# Clean previous outputs
|
|
43
|
+
dotnet clean -c Release -p:RepoRoot="$REPO_ROOT"
|
|
44
|
+
|
|
45
|
+
# Build
|
|
46
|
+
dotnet build -c Release -p:Version=${PACKAGE_VERSION} -p:RepoRoot="$REPO_ROOT"
|
|
47
|
+
|
|
48
|
+
# Pack
|
|
49
|
+
dotnet pack -c Release -p:Version=${PACKAGE_VERSION} -p:RepoRoot="$REPO_ROOT"
|
|
50
|
+
EXIT_CODE=$?
|
|
51
|
+
check_task_status "Pack" $EXIT_CODE
|
|
52
|
+
|
|
53
|
+
# Push
|
|
54
|
+
dotnet nuget push "./packages/${PACKAGE_ID}.*.nupkg" -k ${NUGET_APIKEY} -s ${NUGET_SERVER}
|
|
55
|
+
EXIT_CODE=$?
|
|
56
|
+
check_task_status "NuGet Push" $EXIT_CODE
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<Project Sdk="Microsoft.NET.Sdk">
|
|
2
|
+
|
|
3
|
+
<PropertyGroup>
|
|
4
|
+
<TargetFramework>net7.0</TargetFramework>
|
|
5
|
+
<ImplicitUsings>enable</ImplicitUsings>
|
|
6
|
+
<IsPackable>false</IsPackable>
|
|
7
|
+
<Nullable>enable</Nullable>
|
|
8
|
+
<IsTestProject>true</IsTestProject>
|
|
9
|
+
|
|
10
|
+
</PropertyGroup>
|
|
11
|
+
|
|
12
|
+
<ItemGroup>
|
|
13
|
+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
|
14
|
+
<PackageReference Include="xunit" Version="2.4.2" />
|
|
15
|
+
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
|
16
|
+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
|
17
|
+
<PrivateAssets>all</PrivateAssets>
|
|
18
|
+
</PackageReference>
|
|
19
|
+
<PackageReference Include="coverlet.collector" Version="3.1.2">
|
|
20
|
+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
|
21
|
+
<PrivateAssets>all</PrivateAssets>
|
|
22
|
+
</PackageReference>
|
|
23
|
+
</ItemGroup>
|
|
24
|
+
|
|
25
|
+
<ItemGroup>
|
|
26
|
+
<ProjectReference Include="..\Cloud.Unum.USearch\Cloud.Unum.USearch.csproj" />
|
|
27
|
+
</ItemGroup>
|
|
28
|
+
|
|
29
|
+
<Target Name="PlaceNativeLibrary" AfterTargets="Build">
|
|
30
|
+
<ItemGroup>
|
|
31
|
+
<NativeBinaries Include="$(NativeLibDir)runtimes\$(TargetOS)-$(TargetArchitecture)\native\*.*"/>
|
|
32
|
+
</ItemGroup>
|
|
33
|
+
|
|
34
|
+
<Copy SourceFiles="@(NativeBinaries)" DestinationFolder="$(OutputPath)" />
|
|
35
|
+
<Message Text="NativeLibDir: $(NativeLibDir)" Importance="high"/>
|
|
36
|
+
<Message Text="Files: @(NativeBinaries)" Importance="high"/>
|
|
37
|
+
<Message Text="$(OutputPath)" Importance="high" />
|
|
38
|
+
</Target>
|
|
39
|
+
</Project>
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
using Cloud.Unum.USearch;
|
|
2
|
+
|
|
3
|
+
namespace Cloud.Unum.USearch.Tests;
|
|
4
|
+
|
|
5
|
+
public class InitTests
|
|
6
|
+
{
|
|
7
|
+
// Epsilon for real values comparisons
|
|
8
|
+
private const float Epsilon = 0.000001f;
|
|
9
|
+
|
|
10
|
+
[Fact]
|
|
11
|
+
public void InitializedAndDisposedSucceeds()
|
|
12
|
+
{
|
|
13
|
+
using (var index = new USearchIndex(MetricKind.Cos, ScalarKind.Float32, 3))
|
|
14
|
+
{
|
|
15
|
+
Assert.NotNull(index);
|
|
16
|
+
Assert.Equal((uint)3, index.Dimensions());
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
[Fact]
|
|
21
|
+
public void AddDoubleVectorSucceeds()
|
|
22
|
+
{
|
|
23
|
+
using (var index = new USearchIndex(MetricKind.Cos, ScalarKind.Float64, 3))
|
|
24
|
+
{
|
|
25
|
+
index.Add(1, new double[] { 1.0, 2.0, 3.0 });
|
|
26
|
+
Assert.True(index.Contains(1));
|
|
27
|
+
Assert.False(index.Contains(2));
|
|
28
|
+
Assert.Equal((uint)1, index.Size());
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
[Fact]
|
|
33
|
+
public void AddDoubleVectorsUnderSameKeySucceeds()
|
|
34
|
+
{
|
|
35
|
+
using (var index = new USearchIndex(MetricKind.Cos, ScalarKind.Float64, 3, multi: true))
|
|
36
|
+
{
|
|
37
|
+
index.Add(1, new double[] { 1.0, 2.0, 3.0 });
|
|
38
|
+
index.Add(2, new double[] { 1.0, 2.0, 3.0 });
|
|
39
|
+
index.Add(3, new double[] { 1.0, 2.0, 3.0 });
|
|
40
|
+
index.Add(1, new double[] { 1.0, 2.0, 3.0 });
|
|
41
|
+
index.Add(2, new double[] { 1.0, 2.0, 3.0 });
|
|
42
|
+
Assert.True(index.Contains(1));
|
|
43
|
+
Assert.True(index.Contains(2));
|
|
44
|
+
Assert.True(index.Contains(3));
|
|
45
|
+
Assert.Equal((uint)5, index.Size());
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
[Fact]
|
|
50
|
+
public void ItCanGetMultipleFloatVectorsForSingleKey()
|
|
51
|
+
{
|
|
52
|
+
using (var index = new USearchIndex(MetricKind.Cos, ScalarKind.Float32, 3, multi: true))
|
|
53
|
+
{
|
|
54
|
+
var inputVectors = new float[][] {
|
|
55
|
+
new float[] { 1.0f, 6.0f, 11.0f },
|
|
56
|
+
new float[] { 2.0f, 7.0f, 12.0f },
|
|
57
|
+
new float[] { 3.0f, 8.0f, 13.0f },
|
|
58
|
+
new float[] { 5.0f, 10.0f, 15.0f }
|
|
59
|
+
};
|
|
60
|
+
index.Add(
|
|
61
|
+
new ulong[] { 2, 2, 2, 2 },
|
|
62
|
+
inputVectors
|
|
63
|
+
);
|
|
64
|
+
Assert.True(index.Contains(2));
|
|
65
|
+
Assert.Equal((uint)4, index.Size());
|
|
66
|
+
Assert.Equal(4, index.Count(2));
|
|
67
|
+
|
|
68
|
+
int foundVectorsCount = index.Get(2, 3, out float[][] retrievedVectors);
|
|
69
|
+
Assert.Equal(3, foundVectorsCount);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
[Fact]
|
|
74
|
+
public void ItCanGetMultipleDoubleVectorsForSingleKey()
|
|
75
|
+
{
|
|
76
|
+
using (var index = new USearchIndex(MetricKind.Cos, ScalarKind.Float64, 3, multi: true))
|
|
77
|
+
{
|
|
78
|
+
var inputVectors = new double[][] {
|
|
79
|
+
new double[] { 1.0, 6.0, 11.0 },
|
|
80
|
+
new double[] { 2.0, 7.0, 12.0 },
|
|
81
|
+
new double[] { 3.0, 8.0, 13.0 },
|
|
82
|
+
new double[] { 5.0, 10.0, 15.0 }
|
|
83
|
+
};
|
|
84
|
+
index.Add(
|
|
85
|
+
new ulong[] { 2, 2, 2, 2 },
|
|
86
|
+
inputVectors
|
|
87
|
+
);
|
|
88
|
+
Assert.True(index.Contains(2));
|
|
89
|
+
Assert.Equal((uint)4, index.Size());
|
|
90
|
+
Assert.Equal(4, index.Count(2));
|
|
91
|
+
|
|
92
|
+
int foundVectorsCount = index.Get(2, 3, out double[][] retrievedVectors);
|
|
93
|
+
Assert.Equal(3, foundVectorsCount);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
[Fact]
|
|
98
|
+
public void AddFloatVectorSucceeds()
|
|
99
|
+
{
|
|
100
|
+
using (var index = new USearchIndex(MetricKind.Cos, ScalarKind.Float32, 3))
|
|
101
|
+
{
|
|
102
|
+
index.Add(1, new float[] { 1.0f, 2.0f, 3.0f });
|
|
103
|
+
Assert.Equal((uint)1, index.Size());
|
|
104
|
+
Assert.True(index.Contains(1));
|
|
105
|
+
Assert.False(index.Contains(2));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
[Fact]
|
|
110
|
+
public void AddFloatVectorsUnderSameKeySucceeds()
|
|
111
|
+
{
|
|
112
|
+
using (var index = new USearchIndex(MetricKind.Cos, ScalarKind.Float32, 3, multi: true))
|
|
113
|
+
{
|
|
114
|
+
index.Add(1, new float[] { 1.0f, 2.0f, 3.0f });
|
|
115
|
+
index.Add(2, new float[] { 1.0f, 2.0f, 3.0f });
|
|
116
|
+
index.Add(3, new float[] { 1.0f, 2.0f, 3.0f });
|
|
117
|
+
index.Add(1, new float[] { 1.0f, 2.0f, 3.0f });
|
|
118
|
+
index.Add(2, new float[] { 1.0f, 2.0f, 3.0f });
|
|
119
|
+
Assert.True(index.Contains(1));
|
|
120
|
+
Assert.True(index.Contains(2));
|
|
121
|
+
Assert.True(index.Contains(3));
|
|
122
|
+
Assert.Equal((uint)5, index.Size());
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
[Fact]
|
|
127
|
+
public void AddGetFloatVectorSucceeds()
|
|
128
|
+
{
|
|
129
|
+
using (var index = new USearchIndex(MetricKind.Cos, ScalarKind.Float32, 3))
|
|
130
|
+
{
|
|
131
|
+
var inputVector = new float[] { 1.0f, 2.0f, 3.0f };
|
|
132
|
+
index.Add(1, inputVector);
|
|
133
|
+
Assert.Equal((uint)1, index.Size());
|
|
134
|
+
Assert.True(index.Contains(1));
|
|
135
|
+
Assert.False(index.Contains(2));
|
|
136
|
+
Assert.True(index.Get(1, out float[] retrievedVector) == 1);
|
|
137
|
+
Assert.NotNull(retrievedVector);
|
|
138
|
+
bool areEqual = inputVector
|
|
139
|
+
.Zip(retrievedVector, (a, b) => (a, b))
|
|
140
|
+
.All(pair => Math.Abs(pair.a - pair.b) <= Epsilon);
|
|
141
|
+
|
|
142
|
+
Assert.True(areEqual);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
[Fact]
|
|
147
|
+
public void AddGetDoubleVectorSucceeds()
|
|
148
|
+
{
|
|
149
|
+
using (var index = new USearchIndex(MetricKind.Cos, ScalarKind.Float32, 3))
|
|
150
|
+
{
|
|
151
|
+
var inputVector = new double[] { 1.0, 2.0, 3.33 };
|
|
152
|
+
index.Add(1, inputVector);
|
|
153
|
+
Assert.Equal((uint)1, index.Size());
|
|
154
|
+
Assert.True(index.Contains(1));
|
|
155
|
+
Assert.False(index.Contains(2));
|
|
156
|
+
Assert.True(index.Get(1, out double[] retrievedVector) == 1);
|
|
157
|
+
Assert.NotNull(retrievedVector);
|
|
158
|
+
bool areEqual = inputVector
|
|
159
|
+
.Zip(retrievedVector, (a, b) => (a, b))
|
|
160
|
+
.All(pair => Math.Abs(pair.a - pair.b) <= Epsilon);
|
|
161
|
+
|
|
162
|
+
Assert.True(areEqual);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
[Fact]
|
|
167
|
+
public void ItThrowsException_AddAddWithSameKeyAndGetVector()
|
|
168
|
+
{
|
|
169
|
+
using (var index = new USearchIndex(MetricKind.Cos, ScalarKind.Float32, 3))
|
|
170
|
+
{
|
|
171
|
+
ulong key = 1;
|
|
172
|
+
float[] vector1 = { 1.0f, 2.0f, 33.3f };
|
|
173
|
+
index.Add(key, vector1);
|
|
174
|
+
float[] vector2 = { 3.0f, 4.0f, 34.2f };
|
|
175
|
+
Assert.Throws<USearchException>(() => index.Add(key, vector2));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
[Fact]
|
|
180
|
+
public void TestAddRemoveAddAndGet()
|
|
181
|
+
{
|
|
182
|
+
using (var index = new USearchIndex(MetricKind.Cos, ScalarKind.Float32, 2))
|
|
183
|
+
{
|
|
184
|
+
ulong key = 1;
|
|
185
|
+
float[] vector = { 1.0f, 2.0f };
|
|
186
|
+
|
|
187
|
+
index.Add(key, vector);
|
|
188
|
+
Assert.True(index.Contains(key));
|
|
189
|
+
index.Remove(key);
|
|
190
|
+
Assert.False(index.Contains(key));
|
|
191
|
+
index.Add(key, vector);
|
|
192
|
+
Assert.True(index.Contains(key));
|
|
193
|
+
Assert.True(index.Get(key, out float[] retrievedVector) == 1);
|
|
194
|
+
Assert.Equal(vector, retrievedVector);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
[Fact]
|
|
199
|
+
public void TestAddRemoveAddWithSameKeyVectorAndGet()
|
|
200
|
+
{
|
|
201
|
+
using (var index = new USearchIndex(MetricKind.Cos, ScalarKind.Float32, 2))
|
|
202
|
+
{
|
|
203
|
+
ulong key = 1;
|
|
204
|
+
float[] vector = { 1.0f, 2.0f };
|
|
205
|
+
|
|
206
|
+
index.Add(key, vector);
|
|
207
|
+
Assert.True(index.Contains(key));
|
|
208
|
+
index.Remove(key);
|
|
209
|
+
Assert.False(index.Contains(key));
|
|
210
|
+
index.Add(key, vector);
|
|
211
|
+
Assert.True(index.Contains(key));
|
|
212
|
+
Assert.True(index.Get(key, out float[] retrievedVector) == 1);
|
|
213
|
+
Assert.Equal(vector, retrievedVector);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
[Fact]
|
|
218
|
+
public void TestGetKeyThatNotExist()
|
|
219
|
+
{
|
|
220
|
+
using (var index = new USearchIndex(MetricKind.Cos, ScalarKind.Float32, 2))
|
|
221
|
+
{
|
|
222
|
+
Assert.True(index.Get(1, out float[] retrievedVector) == 0);
|
|
223
|
+
Assert.False(index.Contains(1));
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
[Fact]
|
|
228
|
+
public void Search_FindsExpectedResults()
|
|
229
|
+
{
|
|
230
|
+
using (var index = new USearchIndex(MetricKind.L2sq, ScalarKind.Float32, 2))
|
|
231
|
+
{
|
|
232
|
+
index.Add(
|
|
233
|
+
Enumerable.Range(1, 5).Select(i => (ulong)i).ToArray(),
|
|
234
|
+
new float[][]
|
|
235
|
+
{
|
|
236
|
+
new float[] { -0.5f, -0.6f },
|
|
237
|
+
new float[] { -0.1f, -0.1f },
|
|
238
|
+
new float[] { 0.2f, 0.2f },
|
|
239
|
+
new float[] { 0.7f, 0.8f },
|
|
240
|
+
new float[] { 0.9f, 1.0f }
|
|
241
|
+
}
|
|
242
|
+
);
|
|
243
|
+
var queryVector = new float[] { 0.0f, 0.0f };
|
|
244
|
+
|
|
245
|
+
var matches = index.Search(queryVector, 2, out ulong[] keys, out float[] distances);
|
|
246
|
+
|
|
247
|
+
Assert.Equal(2, matches);
|
|
248
|
+
Assert.Contains(2UL, keys);
|
|
249
|
+
Assert.Contains(3UL, keys);
|
|
250
|
+
Assert.Equal(0.02, distances[0], Epsilon);
|
|
251
|
+
Assert.Equal(0.08, distances[1], Epsilon);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
[Fact]
|
|
256
|
+
public void Search_AfterRemovingKey_FindsExpectedResults()
|
|
257
|
+
{
|
|
258
|
+
using (var index = new USearchIndex(MetricKind.L2sq, ScalarKind.Float32, 2))
|
|
259
|
+
{
|
|
260
|
+
index.Add(1, new float[] { 0.1f, 0.2f });
|
|
261
|
+
index.Add(2, new float[] { 0.2f, 0.3f });
|
|
262
|
+
index.Add(3, new float[] { 0.3f, 0.4f });
|
|
263
|
+
index.Remove(2);
|
|
264
|
+
var queryVector = new float[] { 0.2f, 0.3f };
|
|
265
|
+
|
|
266
|
+
var matches = index.Search(queryVector, 2, out ulong[] keys, out float[] distances);
|
|
267
|
+
|
|
268
|
+
Assert.Equal(2, matches);
|
|
269
|
+
Assert.Contains(1UL, keys);
|
|
270
|
+
Assert.Contains(3UL, keys);
|
|
271
|
+
Assert.DoesNotContain(2UL, keys);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
[Fact]
|
|
276
|
+
public void Search_WithVectorsLessThanResultsLimit()
|
|
277
|
+
{
|
|
278
|
+
using (var index = new USearchIndex(MetricKind.L2sq, ScalarKind.Float32, 2))
|
|
279
|
+
{
|
|
280
|
+
index.Add(1, new float[] { 0.9f, 0.9f });
|
|
281
|
+
index.Add(2, new float[] { 0.8f, 0.8f });
|
|
282
|
+
var queryVector = new float[] { 0.1f, 0.2f };
|
|
283
|
+
|
|
284
|
+
var matches = index.Search(queryVector, 10, out ulong[] keys, out float[] distances);
|
|
285
|
+
|
|
286
|
+
Assert.Equal(2, matches);
|
|
287
|
+
Assert.Contains(1UL, keys);
|
|
288
|
+
Assert.Contains(2UL, keys);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
[Fact]
|
|
293
|
+
public void ItCanRenameKey()
|
|
294
|
+
{
|
|
295
|
+
using (var index = new USearchIndex(MetricKind.L2sq, ScalarKind.Float32, 2))
|
|
296
|
+
{
|
|
297
|
+
index.Add(1, new float[] { 0.9f, 0.9f });
|
|
298
|
+
index.Add(2, new float[] { 0.8f, 0.8f });
|
|
299
|
+
index.Rename(1, 3);
|
|
300
|
+
Assert.False(index.Contains(1));
|
|
301
|
+
Assert.True(index.Contains(2));
|
|
302
|
+
Assert.True(index.Contains(3));
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
global using Xunit;
|
package/docs/benchmarks.md
CHANGED
|
@@ -19,13 +19,13 @@ The default values vary drastically.
|
|
|
19
19
|
Below are the performance numbers for a benchmark running on the 64 cores of AWS `c7g.metal` "Graviton 3"-based instances.
|
|
20
20
|
The main columns are:
|
|
21
21
|
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
22
|
+
- __Add__: Number of insertion Queries Per Second.
|
|
23
|
+
- __Search__: Number search Queries Per Second.
|
|
24
|
+
- __Recall @1__: How often does approximate search yield the exact best match?
|
|
25
25
|
|
|
26
26
|
### Different "connectivity"
|
|
27
27
|
|
|
28
|
-
| Vectors | Connectivity | EF @ A | EF @ S |
|
|
28
|
+
| Vectors | Connectivity | EF @ A | EF @ S | __Add__, QPS | __Search__, QPS | __Recall @1__ |
|
|
29
29
|
| :--------- | :----------: | :----: | :----: | :----------: | :-------------: | ------------: |
|
|
30
30
|
| `f32` x256 | 16 | 128 | 64 | 75'640 | 131'654 | 99.3% |
|
|
31
31
|
| `f32` x256 | 12 | 128 | 64 | 81'747 | 149'728 | 99.0% |
|
|
@@ -33,7 +33,7 @@ The main columns are:
|
|
|
33
33
|
|
|
34
34
|
### Different "expansion factors"
|
|
35
35
|
|
|
36
|
-
| Vectors | Connectivity | EF @ A | EF @ S |
|
|
36
|
+
| Vectors | Connectivity | EF @ A | EF @ S | __Add__, QPS | __Search__, QPS | __Recall @1__ |
|
|
37
37
|
| :--------- | :----------: | :----: | :----: | :----------: | :-------------: | ------------: |
|
|
38
38
|
| `f32` x256 | 16 | 128 | 64 | 75'640 | 131'654 | 99.3% |
|
|
39
39
|
| `f32` x256 | 16 | 64 | 32 | 128'644 | 228'422 | 97.2% |
|
|
@@ -41,7 +41,7 @@ The main columns are:
|
|
|
41
41
|
|
|
42
42
|
### Different vectors "quantization"
|
|
43
43
|
|
|
44
|
-
| Vectors | Connectivity | EF @ A | EF @ S |
|
|
44
|
+
| Vectors | Connectivity | EF @ A | EF @ S | __Add__, QPS | __Search__, QPS | __Recall @1__ |
|
|
45
45
|
| :----------- | :----------: | :----: | :----: | :----------: | :-------------: | ------------: |
|
|
46
46
|
| `f32` x256 | 16 | 128 | 64 | 87'995 | 171'856 | 99.1% |
|
|
47
47
|
| `f16` x256 | 16 | 128 | 64 | 87'270 | 153'788 | 98.4% |
|