photoprism/pkg/vector/distance_test.go
Michael Mayer 03129c9129 Vector: Reorganize package into topic-based files #4669
Split the catch-all values.go into one file per concept, each mirrored
by its test: distance.go, norm.go, stats.go, product.go, centroid.go,
plus the mean methods folded into mean.go and Copy/Dim/Sum into
vector.go. Remove values.go, values_test.go, and values_more_test.go so
functionality and tests live where developers expect them.

Hoist the two 512-dimensional face embeddings shared by the distance,
norm, and cosine tests into fixtures_test.go, removing the previous
triplication, and decompose the monolithic TestVector into per-concept
tests. Close pre-existing coverage gaps in the integer converters and
the GeometricMean/HarmonicMean method wrappers, bringing the package to
100% statement coverage. Pure code movement; no behavior change.
2026-05-30 09:44:03 +00:00

116 lines
4.1 KiB
Go

package vector
import (
"math"
"testing"
"github.com/stretchr/testify/assert"
)
func TestEuclideanDist(t *testing.T) {
a := Vector{1, 2, 3, 4, 6, 5}
b := Vector{2, 1, 3, 4, 5, 6}
d := Vector{0, 0, 0, 0, 0, 0}
e := Vector{}
n := make(Vector, 512)
t.Run("Method", func(t *testing.T) {
assert.InDelta(t, 2, a.EuclideanDist(b), 0.01)
assert.InDelta(t, a.EuclideanDist(b), b.EuclideanDist(a), 0.01)
assert.True(t, math.IsNaN(faceEmbeddingB.EuclideanDist(d)))
assert.InDelta(t, 0, d.EuclideanDist(d), 0.01)
assert.True(t, math.IsNaN(e.EuclideanDist(d)))
assert.InDelta(t, 0.9999999779072661, faceEmbeddingB.EuclideanDist(n), 0.01)
})
t.Run("Func", func(t *testing.T) {
assert.InDelta(t, 2.0, EuclideanDist(a, b), 0.01)
})
}
func TestCosineSimilarity(t *testing.T) {
a := Vector{1, 2, 3, 4, 6, 5}
b := Vector{2, 1, 3, 4, 5, 6}
d := Vector{0, 0, 0, 0, 0, 0}
e := Vector{}
n := make(Vector, 512)
t.Run("Values", func(t *testing.T) {
assert.InDelta(t, 0.978021978021978, a.CosineSimilarity(b), 0.01)
assert.True(t, math.IsNaN(faceEmbeddingB.CosineSimilarity(d)))
assert.InDelta(t, 0, d.CosineSimilarity(d), 0.01)
assert.True(t, math.IsNaN(e.CosineSimilarity(d)))
assert.InDelta(t, 0, faceEmbeddingB.CosineSimilarity(n), 0.01)
assert.InDelta(t, 0, n.CosineSimilarity(n), 0.01)
assert.InDelta(t, 1.0, faceEmbeddingB.CosineSimilarity(faceEmbeddingB), 0.01)
assert.InDelta(t, 1.0, a.CosineSimilarity(a), 0.01)
assert.InDelta(t, 1.0, b.CosineSimilarity(b), 0.01)
})
t.Run("Func", func(t *testing.T) {
assert.InDelta(t, 0.978021978021978, CosineSimilarity(a, b), 0.01)
})
t.Run("Orthogonal", func(t *testing.T) {
assert.InDelta(t, 0.0, CosineSimilarity(Vector{1, 0}, Vector{0, 1}), 0.00001)
assert.InDelta(t, 1.0, CosineDist(Vector{1, 0}, Vector{0, 1}), 0.00001)
})
t.Run("Opposite", func(t *testing.T) {
assert.InDelta(t, -1.0, CosineSimilarity(Vector{1, 0}, Vector{-1, 0}), 0.00001)
assert.InDelta(t, 2.0, CosineDist(Vector{1, 0}, Vector{-1, 0}), 0.00001)
})
t.Run("DimensionMismatch", func(t *testing.T) {
assert.True(t, math.IsNaN(CosineSimilarity(Vector{1, 0}, Vector{1, 0, 0})))
assert.True(t, math.IsNaN(CosineDist(Vector{1, 0}, Vector{1, 0, 0})))
})
}
func TestCosineDist(t *testing.T) {
a := Vector{1, 2, 3, 4, 6, 5}
b := Vector{2, 1, 3, 4, 5, 6}
d := Vector{0, 0, 0, 0, 0, 0}
e := Vector{}
n := make(Vector, 512)
t.Run("Values", func(t *testing.T) {
// Distance is 1 - similarity: 0 for identical, 1 for a zero vector, NaN on dim mismatch.
assert.InDelta(t, 0.021978021978022, a.CosineDist(b), 0.01)
assert.True(t, math.IsNaN(faceEmbeddingB.CosineDist(d)))
assert.InDelta(t, 1.0, d.CosineDist(d), 0.01)
assert.True(t, math.IsNaN(e.CosineDist(d)))
assert.InDelta(t, 1.0, faceEmbeddingB.CosineDist(n), 0.01)
assert.InDelta(t, 1.0, n.CosineDist(n), 0.01)
assert.InDelta(t, 0, faceEmbeddingB.CosineDist(faceEmbeddingB), 0.01)
assert.InDelta(t, 0, a.CosineDist(a), 0.01)
assert.InDelta(t, 0, b.CosineDist(b), 0.01)
})
t.Run("Func", func(t *testing.T) {
assert.InDelta(t, 0.021978021978022, CosineDist(a, b), 0.01)
})
t.Run("Equal", func(t *testing.T) {
x := Vector{1, 0, 0, 1, 0, 0}
y := Vector{1, 0, 0, 1, 0, 0}
// Identical vectors: similarity 1, distance 0.
assert.InDelta(t, 1.0, CosineSimilarity(x, y), 0.00001)
assert.InDelta(t, 0.0, CosineDist(x, y), 0.00001)
})
t.Run("Faces", func(t *testing.T) {
// Real face embeddings: near-orthogonal, so distance is ~1.
assert.InDelta(t, -0.003275301858301365, CosineSimilarity(faceEmbeddingA, faceEmbeddingB), 0.00001)
assert.InDelta(t, 1.003275301858301365, CosineDist(faceEmbeddingA, faceEmbeddingB), 0.00001)
})
}
func TestCosineDists(t *testing.T) {
x := Vectors{{1, 0}, {0, 1}}
y := Vectors{{1, 0}, {-1, 0}}
got := CosineDists(x, y)
assert.Len(t, got, 2)
assert.InDelta(t, 0.0, got[0][0], 0.00001) // identical
assert.InDelta(t, 2.0, got[0][1], 0.00001) // opposite
assert.InDelta(t, 1.0, got[1][0], 0.00001) // orthogonal
assert.InDelta(t, 1.0, got[1][1], 0.00001) // orthogonal
}
func BenchmarkCosineDist(b *testing.B) {
for b.Loop() {
CosineDist(faceEmbeddingA, faceEmbeddingB)
}
}