feat: initial release
Assisted-by: GLM 5.3 Flash
This commit is contained in:
@@ -0,0 +1,256 @@
|
||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package signal
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"sourcedock.dev/petrbalvin/tensor/internal/core"
|
||||
)
|
||||
|
||||
// TestSumKahanRecoversSmallTerms checks the compensation: small terms
|
||||
// between huge ones survive, where a plain left-to-right sum loses
|
||||
// them entirely (the naive sum of this sequence is 0).
|
||||
func TestSumKahanRecoversSmallTerms(t *testing.T) {
|
||||
vals := mustFloats(t, []float64{1e16, 1, 2, -1e16})
|
||||
got, err := SumKahan(vals)
|
||||
if err != nil {
|
||||
t.Fatalf("SumKahan: %v", err)
|
||||
}
|
||||
if got != 4 {
|
||||
t.Fatalf("SumKahan = %g, want 4", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGradient1DDifferentiatesLinesExactly: both the central stencil
|
||||
// and the one-sided endpoints are exact on linear signals.
|
||||
func TestGradient1DDifferentiatesLinesExactly(t *testing.T) {
|
||||
const n = 16
|
||||
const dx = 0.25
|
||||
y := make([]float64, n)
|
||||
for i := range n {
|
||||
y[i] = 3*float64(i)*dx + 7
|
||||
}
|
||||
got, err := Gradient1D(mustFloats(t, y, n), dx)
|
||||
if err != nil {
|
||||
t.Fatalf("Gradient1D: %v", err)
|
||||
}
|
||||
for i := range n {
|
||||
if math.Abs(got.FloatAt(i)-3) > 1e-12 {
|
||||
t.Fatalf("gradient[%d] = %.14g, want 3", i, got.FloatAt(i))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestGradient1DKnownDifferentiatesQuadratics: the central interior
|
||||
// stencil is exact on quadratics; the first-order endpoint stencils
|
||||
// carry an O(dx) offset.
|
||||
func TestGradient1DKnownDifferentiatesQuadratics(t *testing.T) {
|
||||
const n = 16
|
||||
const dx = 0.25
|
||||
y := make([]float64, n)
|
||||
for i := range n {
|
||||
x := float64(i) * dx
|
||||
y[i] = x * x
|
||||
}
|
||||
got, err := Gradient1D(mustFloats(t, y, n), dx)
|
||||
if err != nil {
|
||||
t.Fatalf("Gradient1D: %v", err)
|
||||
}
|
||||
for i := 1; i < n-1; i++ {
|
||||
want := 2 * float64(i) * dx
|
||||
if math.Abs(got.FloatAt(i)-want) > 1e-12 {
|
||||
t.Fatalf("gradient[%d] = %.14g, want %.14g", i, got.FloatAt(i), want)
|
||||
}
|
||||
}
|
||||
if math.Abs(got.FloatAt(0)-dx) > 1e-12 {
|
||||
t.Fatalf("left endpoint = %.14g, want the one-sided 2x+h = %.14g", got.FloatAt(0), dx)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGradient1DErrors pins the validation contract.
|
||||
func TestGradient1DErrors(t *testing.T) {
|
||||
if _, err := Gradient1D(mustFloats(t, []float64{1}), 0.1); err == nil {
|
||||
t.Fatal("expected an error for a single-point signal")
|
||||
}
|
||||
if _, err := Gradient1D(mustFloats(t, []float64{1, 2, 3}), 0); err == nil {
|
||||
t.Fatal("expected an error for a zero spacing")
|
||||
}
|
||||
rank2, _ := core.FromFloats([]float64{1, 2, 3, 4}, 2, 2)
|
||||
if _, err := Gradient1D(rank2, 0.1); err == nil {
|
||||
t.Fatal("expected an error for a rank-2 signal")
|
||||
}
|
||||
}
|
||||
|
||||
// TestLaplacian1DKnownDifferentiatesSines checks the 3-point stencil
|
||||
// against −sin on a full period and the boundary copy at both ends.
|
||||
func TestLaplacian1DKnownDifferentiatesSines(t *testing.T) {
|
||||
const n = 64
|
||||
const dx = 2 * math.Pi / n
|
||||
y := make([]float64, n)
|
||||
for i := range n {
|
||||
y[i] = math.Sin(float64(i) * dx)
|
||||
}
|
||||
got, err := Laplacian(mustFloats(t, y, n), dx)
|
||||
if err != nil {
|
||||
t.Fatalf("Laplacian: %v", err)
|
||||
}
|
||||
// The interior matches −sin; the boundary copies the neighbour, so
|
||||
// it is checked separately below.
|
||||
for i := 1; i < n-1; i++ {
|
||||
want := -math.Sin(float64(i) * dx)
|
||||
if math.Abs(got.FloatAt(i)-want) > 1e-3 {
|
||||
t.Fatalf("laplacian[%d] = %.6g, want %.6g", i, got.FloatAt(i), want)
|
||||
}
|
||||
}
|
||||
// The boundaries copy their nearest interior value.
|
||||
if got.FloatAt(0) != got.FloatAt(1) || got.FloatAt(n-1) != got.FloatAt(n-2) {
|
||||
t.Fatal("the 1-D boundaries did not copy their interior values")
|
||||
}
|
||||
}
|
||||
|
||||
// TestLaplacian2DKnownDifferentiatesSines checks the 5-point stencil
|
||||
// against −2·sin x·cos y and the boundary copy on every edge.
|
||||
func TestLaplacian2DKnownDifferentiatesSines(t *testing.T) {
|
||||
const n = 32
|
||||
const d = 2 * math.Pi / n
|
||||
grid := make([]float64, n*n)
|
||||
for r := range n {
|
||||
for c := range n {
|
||||
grid[r*n+c] = math.Sin(float64(c)*d) * math.Cos(float64(r)*d)
|
||||
}
|
||||
}
|
||||
got, err := Laplacian(mustFloats(t, grid, n, n), d, d)
|
||||
if err != nil {
|
||||
t.Fatalf("Laplacian: %v", err)
|
||||
}
|
||||
// The interior matches −2·sin x·cos y to the stencil's own
|
||||
// truncation error 2h²/12 ≈ 0.0064 at h = 2π/32; the boundary
|
||||
// copies its nearest interior value, checked separately below.
|
||||
for r := 1; r < n-1; r++ {
|
||||
for c := 1; c < n-1; c++ {
|
||||
want := -2 * math.Sin(float64(c)*d) * math.Cos(float64(r)*d)
|
||||
if math.Abs(got.FloatAt(r*n+c)-want) > 8e-3 {
|
||||
t.Fatalf("laplacian[%d,%d] = %.6g, want %.6g", r, c, got.FloatAt(r*n+c), want)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Every boundary point must equal its nearest interior neighbour.
|
||||
for i := range n {
|
||||
if got.FloatAt(i) != got.FloatAt(n+i) {
|
||||
t.Fatalf("top row %d did not copy the row below", i)
|
||||
}
|
||||
if got.FloatAt((n-1)*n+i) != got.FloatAt((n-2)*n+i) {
|
||||
t.Fatalf("bottom row %d did not copy the row above", i)
|
||||
}
|
||||
if got.FloatAt(i*n) != got.FloatAt(i*n+1) {
|
||||
t.Fatalf("left column %d did not copy its interior neighbour", i)
|
||||
}
|
||||
if got.FloatAt(i*n+n-1) != got.FloatAt(i*n+n-2) {
|
||||
t.Fatalf("right column %d did not copy its interior neighbour", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestLaplacian3DKnownDifferentiatesSines checks the 7-point stencil
|
||||
// against −3·sin x·sin y·sin z, and pins the boundary behaviour: all
|
||||
// six faces copy their nearest interior plane (the doc's promise).
|
||||
func TestLaplacian3DKnownDifferentiatesSines(t *testing.T) {
|
||||
const n = 16
|
||||
const d = 2 * math.Pi / n
|
||||
at := func(z, y, x int) float64 {
|
||||
return math.Sin(float64(x)*d) * math.Sin(float64(y)*d) * math.Sin(float64(z)*d)
|
||||
}
|
||||
grid := make([]float64, n*n*n)
|
||||
for k := range n {
|
||||
for j := range n {
|
||||
for i := range n {
|
||||
grid[(k*n+j)*n+i] = at(k, j, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
got, err := Laplacian(mustFloats(t, grid, n, n, n), d, d, d)
|
||||
if err != nil {
|
||||
t.Fatalf("Laplacian: %v", err)
|
||||
}
|
||||
// Interior accuracy first; the six boundary faces are copies, so
|
||||
// they are excluded from the truncation-error bound and checked
|
||||
// against their interior neighbours below.
|
||||
worst := 0.0
|
||||
for k := 1; k < n-1; k++ {
|
||||
for j := 1; j < n-1; j++ {
|
||||
for i := 1; i < n-1; i++ {
|
||||
want := -3 * at(k, j, i)
|
||||
d := math.Abs(got.FloatAt((k*n+j)*n+i) - want)
|
||||
if d > worst {
|
||||
worst = d
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if worst > 5e-2 {
|
||||
// The bound is the stencil's own truncation error 3h²/12 ≈ 0.039
|
||||
// at h = 2π/16; a wrong stencil is orders of magnitude worse.
|
||||
t.Fatalf("7-point stencil error %.3g, want under 5e-2", worst)
|
||||
}
|
||||
// All six boundary faces copy their nearest interior plane.
|
||||
for j := range n {
|
||||
for i := range n {
|
||||
if got.FloatAt(j*n+i) != got.FloatAt((n+j)*n+i) {
|
||||
t.Fatalf("front plane (%d,%d) did not copy the plane behind it", j, i)
|
||||
}
|
||||
if got.FloatAt(((n-1)*n+j)*n+i) != got.FloatAt(((n-2)*n+j)*n+i) {
|
||||
t.Fatalf("back plane (%d,%d) did not copy the plane before it", j, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
for k := range n {
|
||||
for i := range n {
|
||||
if got.FloatAt((k*n)*n+i) != got.FloatAt((k*n+1)*n+i) {
|
||||
t.Fatalf("top face (%d,%d) did not copy the row below", k, i)
|
||||
}
|
||||
if got.FloatAt(((k+1)*n-1)*n+i) != got.FloatAt(((k+1)*n-2)*n+i) {
|
||||
t.Fatalf("bottom face (%d,%d) did not copy the row above", k, i)
|
||||
}
|
||||
}
|
||||
for j := range n {
|
||||
if got.FloatAt((k*n+j)*n) != got.FloatAt((k*n+j)*n+1) {
|
||||
t.Fatalf("left face (%d,%d) did not copy its interior neighbour", k, j)
|
||||
}
|
||||
if got.FloatAt((k*n+j)*n+n-1) != got.FloatAt((k*n+j)*n+n-2) {
|
||||
t.Fatalf("right face (%d,%d) did not copy its interior neighbour", k, j)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestLaplacianDegenerateErrors pins the extent contract: every rank-1
|
||||
// signal needs at least 2 points and every axis of a rank-2 or rank-3
|
||||
// grid at least 3, so the stencil and the boundary copies have
|
||||
// somewhere to stand.
|
||||
func TestLaplacianDegenerateErrors(t *testing.T) {
|
||||
if _, err := Laplacian(mustFloats(t, []float64{1}), 0.1); err == nil {
|
||||
t.Fatal("expected an error for a single-point 1-D signal")
|
||||
}
|
||||
tiny2D, _ := core.FromFloats(make([]float64, 16), 2, 8)
|
||||
if _, err := Laplacian(tiny2D, 0.1, 0.1); err == nil {
|
||||
t.Fatal("expected an error for a 2-D grid with an extent of 2")
|
||||
}
|
||||
tiny3D, _ := core.FromFloats(make([]float64, 32), 2, 4, 4)
|
||||
if _, err := Laplacian(tiny3D, 0.1, 0.1, 0.1); err == nil {
|
||||
t.Fatal("expected an error for a 3-D grid with an extent of 2")
|
||||
}
|
||||
twoD, _ := core.FromFloats(make([]float64, 9), 3, 3)
|
||||
if _, err := Laplacian(twoD, 0.1); err == nil {
|
||||
t.Fatal("expected an error for a missing spacing")
|
||||
}
|
||||
if _, err := Laplacian(mustFloats(t, []float64{1, 2, 3}), 0); err == nil {
|
||||
t.Fatal("expected an error for a zero spacing")
|
||||
}
|
||||
rank4, _ := core.FromFloats(make([]float64, 16), 2, 2, 2, 2)
|
||||
if _, err := Laplacian(rank4, 0.1, 0.1, 0.1, 0.1); err == nil {
|
||||
t.Fatal("expected an error for a rank-4 array")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user