181 lines
5.1 KiB
Go
181 lines
5.1 KiB
Go
|
|
package poindexter
|
||
|
|
|
||
|
|
import (
|
||
|
|
"fmt"
|
||
|
|
"math/rand"
|
||
|
|
"testing"
|
||
|
|
)
|
||
|
|
|
||
|
|
// deterministicRand returns a rand.Rand with a fixed seed for reproducible datasets.
|
||
|
|
func deterministicRand() *rand.Rand { return rand.New(rand.NewSource(42)) }
|
||
|
|
|
||
|
|
func makeUniformPoints(n, dim int) []KDPoint[int] {
|
||
|
|
r := deterministicRand()
|
||
|
|
pts := make([]KDPoint[int], n)
|
||
|
|
for i := 0; i < n; i++ {
|
||
|
|
coords := make([]float64, dim)
|
||
|
|
for d := 0; d < dim; d++ {
|
||
|
|
coords[d] = r.Float64()
|
||
|
|
}
|
||
|
|
pts[i] = KDPoint[int]{ID: fmt.Sprint(i), Coords: coords, Value: i}
|
||
|
|
}
|
||
|
|
return pts
|
||
|
|
}
|
||
|
|
|
||
|
|
// makeClusteredPoints creates n points around c clusters with small variance.
|
||
|
|
func makeClusteredPoints(n, dim, c int) []KDPoint[int] {
|
||
|
|
if c <= 0 {
|
||
|
|
c = 1
|
||
|
|
}
|
||
|
|
r := deterministicRand()
|
||
|
|
centers := make([][]float64, c)
|
||
|
|
for i := 0; i < c; i++ {
|
||
|
|
centers[i] = make([]float64, dim)
|
||
|
|
for d := 0; d < dim; d++ {
|
||
|
|
centers[i][d] = r.Float64()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
pts := make([]KDPoint[int], n)
|
||
|
|
for i := 0; i < n; i++ {
|
||
|
|
coords := make([]float64, dim)
|
||
|
|
cent := centers[r.Intn(c)]
|
||
|
|
for d := 0; d < dim; d++ {
|
||
|
|
// small gaussian noise around center (Box-Muller)
|
||
|
|
u1 := r.Float64()
|
||
|
|
u2 := r.Float64()
|
||
|
|
z := (rand.NormFloat64()) // uses global; fine for test speed
|
||
|
|
_ = u1
|
||
|
|
_ = u2
|
||
|
|
coords[d] = cent[d] + 0.03*z
|
||
|
|
if coords[d] < 0 {
|
||
|
|
coords[d] = 0
|
||
|
|
} else if coords[d] > 1 {
|
||
|
|
coords[d] = 1
|
||
|
|
}
|
||
|
|
}
|
||
|
|
pts[i] = KDPoint[int]{ID: fmt.Sprint(i), Coords: coords, Value: i}
|
||
|
|
}
|
||
|
|
return pts
|
||
|
|
}
|
||
|
|
|
||
|
|
func benchNearestBackend(b *testing.B, n, dim int, backend KDBackend, uniform bool, clusters int) {
|
||
|
|
var pts []KDPoint[int]
|
||
|
|
if uniform {
|
||
|
|
pts = makeUniformPoints(n, dim)
|
||
|
|
} else {
|
||
|
|
pts = makeClusteredPoints(n, dim, clusters)
|
||
|
|
}
|
||
|
|
tr, _ := NewKDTree(pts, WithBackend(backend))
|
||
|
|
q := make([]float64, dim)
|
||
|
|
for i := range q {
|
||
|
|
q[i] = 0.5
|
||
|
|
}
|
||
|
|
b.ResetTimer()
|
||
|
|
for i := 0; i < b.N; i++ {
|
||
|
|
_, _, _ = tr.Nearest(q)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func benchKNNBackend(b *testing.B, n, dim, k int, backend KDBackend, uniform bool, clusters int) {
|
||
|
|
var pts []KDPoint[int]
|
||
|
|
if uniform {
|
||
|
|
pts = makeUniformPoints(n, dim)
|
||
|
|
} else {
|
||
|
|
pts = makeClusteredPoints(n, dim, clusters)
|
||
|
|
}
|
||
|
|
tr, _ := NewKDTree(pts, WithBackend(backend))
|
||
|
|
q := make([]float64, dim)
|
||
|
|
for i := range q {
|
||
|
|
q[i] = 0.5
|
||
|
|
}
|
||
|
|
b.ResetTimer()
|
||
|
|
for i := 0; i < b.N; i++ {
|
||
|
|
_, _ = tr.KNearest(q, k)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func benchRadiusBackend(b *testing.B, n, dim int, r float64, backend KDBackend, uniform bool, clusters int) {
|
||
|
|
var pts []KDPoint[int]
|
||
|
|
if uniform {
|
||
|
|
pts = makeUniformPoints(n, dim)
|
||
|
|
} else {
|
||
|
|
pts = makeClusteredPoints(n, dim, clusters)
|
||
|
|
}
|
||
|
|
tr, _ := NewKDTree(pts, WithBackend(backend))
|
||
|
|
q := make([]float64, dim)
|
||
|
|
for i := range q {
|
||
|
|
q[i] = 0.5
|
||
|
|
}
|
||
|
|
b.ResetTimer()
|
||
|
|
for i := 0; i < b.N; i++ {
|
||
|
|
_, _ = tr.Radius(q, r)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Uniform 2D/4D, Linear vs Gonum (opt-in via build tag; falls back to linear if not available)
|
||
|
|
func BenchmarkNearest_Linear_Uniform_1k_2D(b *testing.B) {
|
||
|
|
benchNearestBackend(b, 1_000, 2, BackendLinear, true, 0)
|
||
|
|
}
|
||
|
|
func BenchmarkNearest_Gonum_Uniform_1k_2D(b *testing.B) {
|
||
|
|
benchNearestBackend(b, 1_000, 2, BackendGonum, true, 0)
|
||
|
|
}
|
||
|
|
func BenchmarkNearest_Linear_Uniform_10k_2D(b *testing.B) {
|
||
|
|
benchNearestBackend(b, 10_000, 2, BackendLinear, true, 0)
|
||
|
|
}
|
||
|
|
func BenchmarkNearest_Gonum_Uniform_10k_2D(b *testing.B) {
|
||
|
|
benchNearestBackend(b, 10_000, 2, BackendGonum, true, 0)
|
||
|
|
}
|
||
|
|
|
||
|
|
func BenchmarkNearest_Linear_Uniform_1k_4D(b *testing.B) {
|
||
|
|
benchNearestBackend(b, 1_000, 4, BackendLinear, true, 0)
|
||
|
|
}
|
||
|
|
func BenchmarkNearest_Gonum_Uniform_1k_4D(b *testing.B) {
|
||
|
|
benchNearestBackend(b, 1_000, 4, BackendGonum, true, 0)
|
||
|
|
}
|
||
|
|
func BenchmarkNearest_Linear_Uniform_10k_4D(b *testing.B) {
|
||
|
|
benchNearestBackend(b, 10_000, 4, BackendLinear, true, 0)
|
||
|
|
}
|
||
|
|
func BenchmarkNearest_Gonum_Uniform_10k_4D(b *testing.B) {
|
||
|
|
benchNearestBackend(b, 10_000, 4, BackendGonum, true, 0)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Clustered 2D/4D (3 clusters)
|
||
|
|
func BenchmarkNearest_Linear_Clustered_1k_2D(b *testing.B) {
|
||
|
|
benchNearestBackend(b, 1_000, 2, BackendLinear, false, 3)
|
||
|
|
}
|
||
|
|
func BenchmarkNearest_Gonum_Clustered_1k_2D(b *testing.B) {
|
||
|
|
benchNearestBackend(b, 1_000, 2, BackendGonum, false, 3)
|
||
|
|
}
|
||
|
|
func BenchmarkNearest_Linear_Clustered_10k_2D(b *testing.B) {
|
||
|
|
benchNearestBackend(b, 10_000, 2, BackendLinear, false, 3)
|
||
|
|
}
|
||
|
|
func BenchmarkNearest_Gonum_Clustered_10k_2D(b *testing.B) {
|
||
|
|
benchNearestBackend(b, 10_000, 2, BackendGonum, false, 3)
|
||
|
|
}
|
||
|
|
|
||
|
|
func BenchmarkKNN10_Linear_Uniform_10k_2D(b *testing.B) {
|
||
|
|
benchKNNBackend(b, 10_000, 2, 10, BackendLinear, true, 0)
|
||
|
|
}
|
||
|
|
func BenchmarkKNN10_Gonum_Uniform_10k_2D(b *testing.B) {
|
||
|
|
benchKNNBackend(b, 10_000, 2, 10, BackendGonum, true, 0)
|
||
|
|
}
|
||
|
|
func BenchmarkKNN10_Linear_Clustered_10k_2D(b *testing.B) {
|
||
|
|
benchKNNBackend(b, 10_000, 2, 10, BackendLinear, false, 3)
|
||
|
|
}
|
||
|
|
func BenchmarkKNN10_Gonum_Clustered_10k_2D(b *testing.B) {
|
||
|
|
benchKNNBackend(b, 10_000, 2, 10, BackendGonum, false, 3)
|
||
|
|
}
|
||
|
|
|
||
|
|
func BenchmarkRadiusMid_Linear_Uniform_10k_2D(b *testing.B) {
|
||
|
|
benchRadiusBackend(b, 10_000, 2, 0.5, BackendLinear, true, 0)
|
||
|
|
}
|
||
|
|
func BenchmarkRadiusMid_Gonum_Uniform_10k_2D(b *testing.B) {
|
||
|
|
benchRadiusBackend(b, 10_000, 2, 0.5, BackendGonum, true, 0)
|
||
|
|
}
|
||
|
|
func BenchmarkRadiusMid_Linear_Clustered_10k_2D(b *testing.B) {
|
||
|
|
benchRadiusBackend(b, 10_000, 2, 0.5, BackendLinear, false, 3)
|
||
|
|
}
|
||
|
|
func BenchmarkRadiusMid_Gonum_Clustered_10k_2D(b *testing.B) {
|
||
|
|
benchRadiusBackend(b, 10_000, 2, 0.5, BackendGonum, false, 3)
|
||
|
|
}
|