2025-11-03 18:19:12 +00:00
|
|
|
package poindexter
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"math/rand"
|
|
|
|
|
"testing"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// FuzzKDTreeNearest_NoPanic ensures Nearest never panics and distances are non-negative.
|
|
|
|
|
func FuzzKDTreeNearest_NoPanic(f *testing.F) {
|
|
|
|
|
// Seed with small cases
|
|
|
|
|
f.Add(3, 2)
|
|
|
|
|
f.Add(5, 4)
|
|
|
|
|
f.Fuzz(func(t *testing.T, n int, dim int) {
|
|
|
|
|
if n <= 0 {
|
|
|
|
|
n = 1
|
|
|
|
|
}
|
|
|
|
|
if n > 64 {
|
|
|
|
|
n = 64
|
|
|
|
|
}
|
|
|
|
|
if dim <= 0 {
|
|
|
|
|
dim = 1
|
|
|
|
|
}
|
|
|
|
|
if dim > 8 {
|
|
|
|
|
dim = 8
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pts := make([]KDPoint[int], n)
|
|
|
|
|
for i := 0; i < n; i++ {
|
|
|
|
|
coords := make([]float64, dim)
|
|
|
|
|
for d := 0; d < dim; d++ {
|
|
|
|
|
coords[d] = rand.Float64()*100 - 50
|
|
|
|
|
}
|
|
|
|
|
pts[i] = KDPoint[int]{ID: "", Coords: coords, Value: i}
|
|
|
|
|
}
|
|
|
|
|
tr, err := NewKDTree(pts)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Skip()
|
|
|
|
|
}
|
|
|
|
|
q := make([]float64, dim)
|
|
|
|
|
for d := range q {
|
|
|
|
|
q[d] = rand.Float64()*100 - 50
|
|
|
|
|
}
|
|
|
|
|
_, dist, _ := tr.Nearest(q)
|
|
|
|
|
if dist < 0 {
|
|
|
|
|
t.Fatalf("negative distance: %v", dist)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// FuzzMetrics_NoNegative checks Manhattan, Euclidean, Chebyshev don't return negatives for random inputs.
|
|
|
|
|
func FuzzMetrics_NoNegative(f *testing.F) {
|
|
|
|
|
f.Add(2)
|
|
|
|
|
f.Add(4)
|
|
|
|
|
f.Fuzz(func(t *testing.T, dim int) {
|
|
|
|
|
if dim <= 0 {
|
|
|
|
|
dim = 1
|
|
|
|
|
}
|
|
|
|
|
if dim > 8 {
|
|
|
|
|
dim = 8
|
|
|
|
|
}
|
|
|
|
|
a := make([]float64, dim)
|
|
|
|
|
b := make([]float64, dim)
|
|
|
|
|
for i := 0; i < dim; i++ {
|
|
|
|
|
a[i] = rand.Float64()*10 - 5
|
|
|
|
|
b[i] = rand.Float64()*10 - 5
|
|
|
|
|
}
|
|
|
|
|
m1 := EuclideanDistance{}.Distance(a, b)
|
|
|
|
|
m2 := ManhattanDistance{}.Distance(a, b)
|
|
|
|
|
m3 := ChebyshevDistance{}.Distance(a, b)
|
2025-11-03 18:51:23 +00:00
|
|
|
m4 := CosineDistance{}.Distance(a, b)
|
|
|
|
|
w := make([]float64, dim)
|
|
|
|
|
for i := range w {
|
|
|
|
|
w[i] = 1
|
|
|
|
|
}
|
|
|
|
|
m5 := WeightedCosineDistance{Weights: w}.Distance(a, b)
|
|
|
|
|
if m1 < 0 || m2 < 0 || m3 < 0 || m4 < 0 || m5 < 0 {
|
|
|
|
|
t.Fatalf("negative metric: %v %v %v %v %v", m1, m2, m3, m4, m5)
|
|
|
|
|
}
|
|
|
|
|
if m4 > 2 || m5 > 2 {
|
|
|
|
|
t.Fatalf("cosine distance out of bounds: %v %v", m4, m5)
|
2025-11-03 18:19:12 +00:00
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// FuzzDimensionMismatch_NoPanic ensures queries with wrong dims return ok=false and not panic.
|
|
|
|
|
func FuzzDimensionMismatch_NoPanic(f *testing.F) {
|
|
|
|
|
f.Add(3, 2, 1)
|
|
|
|
|
f.Fuzz(func(t *testing.T, n, dim, qdim int) {
|
|
|
|
|
if n <= 0 {
|
|
|
|
|
n = 1
|
|
|
|
|
}
|
|
|
|
|
if n > 32 {
|
|
|
|
|
n = 32
|
|
|
|
|
}
|
|
|
|
|
if dim <= 0 {
|
|
|
|
|
dim = 1
|
|
|
|
|
}
|
|
|
|
|
if dim > 6 {
|
|
|
|
|
dim = 6
|
|
|
|
|
}
|
|
|
|
|
if qdim < 0 {
|
|
|
|
|
qdim = 0
|
|
|
|
|
}
|
|
|
|
|
if qdim > 6 {
|
|
|
|
|
qdim = 6
|
|
|
|
|
}
|
|
|
|
|
pts := make([]KDPoint[int], n)
|
|
|
|
|
for i := 0; i < n; i++ {
|
|
|
|
|
coords := make([]float64, dim)
|
|
|
|
|
pts[i] = KDPoint[int]{Coords: coords}
|
|
|
|
|
}
|
|
|
|
|
tr, err := NewKDTree(pts)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Skip()
|
|
|
|
|
}
|
|
|
|
|
q := make([]float64, qdim)
|
|
|
|
|
_, _, ok := tr.Nearest(q)
|
|
|
|
|
if qdim != dim && ok {
|
|
|
|
|
t.Fatalf("expected ok=false for dim mismatch; dim=%d qdim=%d", dim, qdim)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|