feat: initial release
Release / gates (push) Successful in 4m38s
Test / test (push) Successful in 5m16s
Release / release (push) Successful in 35s

Assisted-by: GLM 5.3 Flash
This commit is contained in:
2026-09-03 10:00:00 +02:00
commit af4ee19703
617 changed files with 191195 additions and 0 deletions
+99
View File
@@ -0,0 +1,99 @@
// 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"
)
// The spectral Poisson solves read their source through poissonFloats,
// which must materialise a rebased view in element order. A view's
// payload is longer than its element count and starts past offset zero,
// so a raw payload read would solve a different problem: this pin
// compares a 2-D view answer against the same values in a fresh array,
// bit for bit, on both solvers.
func TestSolvePoissonViewsMatchFresh(t *testing.T) {
const rows, cols = 9, 9
win := make([]float64, rows*cols)
for i := range win {
win[i] = math.Sin(float64(i)*0.37)*0.5 + float64(i%7)*0.1 - 0.3
}
// Neumann's compatibility gate measures the source with the
// trapezoidal rule: interior weight 1, edge weight 1/2, corner
// weight 1/4. Subtract the trapezoidal mean so the window's measure
// is exactly zero.
weights := make([]float64, rows*cols)
wSum := 0.0
for r := range rows {
for c := range cols {
w := 1.0
if r == 0 || r == rows-1 {
w /= 2
}
if c == 0 || c == cols-1 {
w /= 2
}
weights[r*cols+c] = w
wSum += w
}
}
mean := 0.0
for i := range win {
mean += weights[i] * win[i]
}
mean /= wSum
for i := range win {
win[i] -= mean
}
padded := make([]float64, (rows+2)*(cols+2))
for r := range rows {
copy(padded[(r+1)*(cols+2)+1:(r+1)*(cols+2)+1+cols], win[r*cols:(r+1)*cols])
}
back, err := core.FromFloats(padded, rows+2, cols+2)
if err != nil {
t.Fatal(err)
}
v1, err := core.Slice(back, 0, 1, rows+1)
if err != nil {
t.Fatal(err)
}
view, err := core.Slice(v1, 1, 1, cols+1)
if err != nil {
t.Fatal(err)
}
fresh, err := core.FromFloats(win, rows, cols)
if err != nil {
t.Fatal(err)
}
solvers := map[string]func(*core.Array) (*core.Array, error){
"SolvePoissonDirichlet": func(a *core.Array) (*core.Array, error) {
return SolvePoissonDirichlet(a, 1, 1)
},
"SolvePoissonNeumann": func(a *core.Array) (*core.Array, error) {
return SolvePoissonNeumann(a, 1, 1)
},
}
for name, solve := range solvers {
gv, err := solve(view)
if err != nil {
t.Fatalf("%s(view): %v", name, err)
}
gf, err := solve(fresh)
if err != nil {
t.Fatalf("%s(fresh): %v", name, err)
}
if gv.Len() != gf.Len() {
t.Fatalf("%s: view length %d, fresh %d", name, gv.Len(), gf.Len())
}
for i := range gv.Len() {
if math.Float64bits(gv.FloatAt(i)) != math.Float64bits(gf.FloatAt(i)) {
t.Fatalf("%s element %d: view %#x, fresh %#x",
name, i, math.Float64bits(gv.FloatAt(i)), math.Float64bits(gf.FloatAt(i)))
}
}
}
}