All checks were successful
release-tag / release-image (push) Successful in 2m32s
146 lines
5.2 KiB
Go
146 lines
5.2 KiB
Go
package vectorgraph
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
func TestBuildLinksNearbyVectorsWithoutModelDependency(t *testing.T) {
|
|
entries := []Entry{
|
|
{ID: "a", Vector: []float32{1, 0, 0, 0}},
|
|
{ID: "b", Vector: []float32{.99, .02, 0, 0}},
|
|
{ID: "c", Vector: []float32{.98, -.01, 0, 0}},
|
|
{ID: "x", Vector: []float32{0, 1, 0, 0}},
|
|
{ID: "y", Vector: []float32{0, .99, .02, 0}},
|
|
}
|
|
cfg := Config{Neighbors: 2, CandidateLimit: 32, HashBits: 16, HashTables: 2, BandBits: 8, MinSimilarity: .8, MinAffinity: .2}
|
|
result := Build(entries, cfg)
|
|
if len(result.Links) == 0 {
|
|
t.Fatal("expected semantic links")
|
|
}
|
|
seenAB := false
|
|
for _, link := range result.Links {
|
|
if (link.Source == "a" && link.Target == "b") || (link.Source == "b" && link.Target == "a") {
|
|
seenAB = true
|
|
}
|
|
if (link.Source == "a" && link.Target == "x") || (link.Source == "x" && link.Target == "a") {
|
|
t.Fatalf("orthogonal vectors must not link: %+v", link)
|
|
}
|
|
}
|
|
if !seenAB {
|
|
t.Fatalf("expected a/b to be linked: %+v", result.Links)
|
|
}
|
|
}
|
|
|
|
func TestBuildIsDeterministicAndProducesFiniteLayout(t *testing.T) {
|
|
entries := []Entry{
|
|
{ID: "a", Vector: []float32{1, .2, .1, 0}},
|
|
{ID: "b", Vector: []float32{.95, .22, .08, 0}},
|
|
{ID: "c", Vector: []float32{0, 1, .1, .2}},
|
|
{ID: "d", Vector: []float32{0, .96, .12, .18}},
|
|
}
|
|
cfg := Config{Neighbors: 2, CandidateLimit: 32, HashBits: 16, HashTables: 2, BandBits: 8, MinSimilarity: .7, MinAffinity: .2, Layout: true}
|
|
a := Build(entries, cfg)
|
|
b := Build(entries, cfg)
|
|
if !reflect.DeepEqual(a.Links, b.Links) || !reflect.DeepEqual(a.Positions, b.Positions) {
|
|
t.Fatal("vector graph must be deterministic")
|
|
}
|
|
if len(a.Positions) != len(entries) {
|
|
t.Fatalf("expected %d positions, got %d", len(entries), len(a.Positions))
|
|
}
|
|
for _, p := range a.Positions {
|
|
if math.IsNaN(p.X) || math.IsNaN(p.Y) || math.IsNaN(p.Z) || math.IsInf(p.X, 0) || math.IsInf(p.Y, 0) || math.IsInf(p.Z, 0) {
|
|
t.Fatalf("invalid position: %+v", p)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBuildFocusedOnlyAnchorsRequestedOrphansAgainstFullCorpus(t *testing.T) {
|
|
entries := []Entry{
|
|
{ID: "connected-a", Vector: []float32{1, 0, 0, 0}},
|
|
{ID: "connected-b", Vector: []float32{.99, .01, 0, 0}},
|
|
{ID: "orphan", Vector: []float32{.985, .015, 0, 0}},
|
|
{ID: "far", Vector: []float32{0, 1, 0, 0}},
|
|
}
|
|
cfg := Config{Neighbors: 2, CandidateLimit: 32, HashBits: 16, HashTables: 2, BandBits: 8, MinSimilarity: .8, MinAffinity: .2}
|
|
result := BuildFocused(entries, map[string]bool{"orphan": true}, cfg)
|
|
if result.Stats.Focused != 1 {
|
|
t.Fatalf("expected one focused entry, got %+v", result.Stats)
|
|
}
|
|
if len(result.Links) == 0 {
|
|
t.Fatal("expected focused orphan to link into full corpus")
|
|
}
|
|
for _, link := range result.Links {
|
|
if link.Source != "orphan" && link.Target != "orphan" {
|
|
t.Fatalf("second pass must not invent links between non-focused nodes: %+v", link)
|
|
}
|
|
if link.Source == "far" || link.Target == "far" {
|
|
t.Fatalf("orthogonal candidate must remain unlinked: %+v", link)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBuildFocusedIsDeterministic(t *testing.T) {
|
|
entries := []Entry{
|
|
{ID: "a", Vector: []float32{1, .1, 0, 0}},
|
|
{ID: "b", Vector: []float32{.99, .11, 0, 0}},
|
|
{ID: "c", Vector: []float32{.97, .13, 0, 0}},
|
|
}
|
|
cfg := Config{Neighbors: 2, CandidateLimit: 32, HashBits: 16, HashTables: 2, BandBits: 8, MinSimilarity: .7, MinAffinity: .1}
|
|
focus := map[string]bool{"c": true}
|
|
a := BuildFocused(entries, focus, cfg)
|
|
b := BuildFocused(entries, focus, cfg)
|
|
if !reflect.DeepEqual(a, b) {
|
|
t.Fatalf("focused vector graph must be deterministic: a=%+v b=%+v", a, b)
|
|
}
|
|
}
|
|
|
|
func TestSpreadDenseLayoutSeparatesDenseCellDeterministically(t *testing.T) {
|
|
entries := make([]indexedEntry, 24)
|
|
positions := make([][3]float64, 24)
|
|
for i := range entries {
|
|
entries[i] = indexedEntry{Entry: Entry{ID: fmt.Sprintf("node-%02d", i)}}
|
|
positions[i] = [3]float64{.1, .1, .1}
|
|
}
|
|
a := spreadDenseLayout(entries, append([][3]float64(nil), positions...))
|
|
b := spreadDenseLayout(entries, append([][3]float64(nil), positions...))
|
|
if !reflect.DeepEqual(a, b) {
|
|
t.Fatal("dense layout relaxation must be deterministic")
|
|
}
|
|
unique := map[[3]float64]bool{}
|
|
for _, p := range a {
|
|
unique[p] = true
|
|
}
|
|
if len(unique) < 8 {
|
|
t.Fatalf("dense cell was not meaningfully spread, unique positions=%d", len(unique))
|
|
}
|
|
}
|
|
|
|
func TestParallelWorkersMatchSingleWorker(t *testing.T) {
|
|
entries := make([]Entry, 96)
|
|
for i := range entries {
|
|
entries[i] = Entry{ID: fmt.Sprintf("n-%03d", i), Vector: []float32{
|
|
float32((i%11)+1) / 11,
|
|
float32((i%7)+1) / 7,
|
|
float32((i%5)+1) / 5,
|
|
float32((i%3)+1) / 3,
|
|
}}
|
|
}
|
|
base := Config{Workers: 1, Neighbors: 4, CandidateLimit: 48, HashBits: 16, HashTables: 2, BandBits: 8, MinSimilarity: .65, MinAffinity: .1, Layout: true}
|
|
parallel := base
|
|
parallel.Workers = 8
|
|
one := Build(entries, base)
|
|
many := Build(entries, parallel)
|
|
if !reflect.DeepEqual(one, many) {
|
|
t.Fatalf("parallel primary build differs from single-worker build")
|
|
}
|
|
focus := map[string]bool{"n-003": true, "n-017": true, "n-044": true, "n-081": true}
|
|
oneFocused := BuildFocused(entries, focus, base)
|
|
manyFocused := BuildFocused(entries, focus, parallel)
|
|
if !reflect.DeepEqual(oneFocused, manyFocused) {
|
|
t.Fatalf("parallel focused build differs from single-worker build")
|
|
}
|
|
}
|