Tooling
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
package field
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/rand/v2"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// The deque has to give the same answer as the loop it replaces, on every cell, including the edges and the
|
||||
// seam. It is O(1) a cell against O(radius squared), which is the difference between a diagnostic and a hang
|
||||
// at planet scale - and an optimisation that is only nearly right is worse than the version that was slow.
|
||||
func TestSlidingWindowsMatchTheNaiveLoop(t *testing.T) {
|
||||
r := rand.New(rand.NewPCG(7, 9))
|
||||
const w, h = 61, 37
|
||||
f := New(w, h, 8)
|
||||
for i := range f.Data {
|
||||
f.Data[i] = float32(r.NormFloat64() * 50)
|
||||
}
|
||||
|
||||
naive := func(cx, cy, radius int, wrapX, wantMax bool) float32 {
|
||||
best := float32(math.Inf(1))
|
||||
if wantMax {
|
||||
best = float32(math.Inf(-1))
|
||||
}
|
||||
for y := cy - radius; y <= cy+radius; y++ {
|
||||
sy := y
|
||||
if sy < 0 {
|
||||
sy = 0
|
||||
} else if sy >= h {
|
||||
sy = h - 1
|
||||
}
|
||||
for x := cx - radius; x <= cx+radius; x++ {
|
||||
sx := x
|
||||
if wrapX {
|
||||
sx = ((sx % w) + w) % w
|
||||
} else if sx < 0 {
|
||||
sx = 0
|
||||
} else if sx >= w {
|
||||
sx = w - 1
|
||||
}
|
||||
v := f.Data[sy*w+sx]
|
||||
if (wantMax && v > best) || (!wantMax && v < best) {
|
||||
best = v
|
||||
}
|
||||
}
|
||||
}
|
||||
return best
|
||||
}
|
||||
|
||||
for _, radius := range []int{1, 3, 8, 20} {
|
||||
for _, wrapX := range []bool{false, true} {
|
||||
hi := SlidingMax(f, radius, wrapX)
|
||||
lo := SlidingMin(f, radius, wrapX)
|
||||
rel := LocalRelief(f, radius, wrapX)
|
||||
for y := 0; y < h; y++ {
|
||||
for x := 0; x < w; x++ {
|
||||
i := y*w + x
|
||||
if got, want := hi.Data[i], naive(x, y, radius, wrapX, true); got != want {
|
||||
t.Fatalf("max r=%d wrap=%v at (%d,%d): %v want %v", radius, wrapX, x, y, got, want)
|
||||
}
|
||||
if got, want := lo.Data[i], naive(x, y, radius, wrapX, false); got != want {
|
||||
t.Fatalf("min r=%d wrap=%v at (%d,%d): %v want %v", radius, wrapX, x, y, got, want)
|
||||
}
|
||||
if got := rel.Data[i]; got != hi.Data[i]-lo.Data[i] {
|
||||
t.Fatalf("relief r=%d at (%d,%d): %v against %v", radius, x, y, got,
|
||||
hi.Data[i]-lo.Data[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A radius of zero is a no-op rather than an error, which is what a caller with a window smaller than one
|
||||
// cell should get.
|
||||
func TestASlidingWindowOfNothingIsTheFieldItself(t *testing.T) {
|
||||
f := New(4, 3, 8)
|
||||
for i := range f.Data {
|
||||
f.Data[i] = float32(i)
|
||||
}
|
||||
for _, got := range []*Field{SlidingMax(f, 0, true), SlidingMin(f, 0, false)} {
|
||||
for i := range f.Data {
|
||||
if got.Data[i] != f.Data[i] {
|
||||
t.Fatalf("radius 0 changed cell %d", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
if rel := LocalRelief(f, 0, true); rel.Data[5] != 0 {
|
||||
t.Errorf("relief over a single cell is zero, got %v", rel.Data[5])
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user